From ae1042746a0c25ac493f7278d1d37e476915d6cc Mon Sep 17 00:00:00 2001 From: Dennis Thiele <dennis.thiele@stud.hs-bochum.de> Date: Wed, 12 Jul 2023 10:16:22 +0200 Subject: [PATCH] WS Anlegen --- ros2_ws/build/.built_by | 1 + ros2_ws/build/COLCON_IGNORE | 0 .../sphero_mini_controller/GameWavefront.py | 375 +++++++++ .../WavefrontChatGpt.py | 53 ++ .../WavefrontPlanner.py | 397 +++++++++ .../lib/sphero_mini_controller/__init__.py | 0 .../lib/sphero_mini_controller/_constants.py | 72 ++ .../lib/sphero_mini_controller/a_star.py | 282 +++++++ .../sphero_mini_controller/blobErkennung.py | 80 ++ .../dynamicPathPlanning.py | 504 ++++++++++++ .../build/lib/sphero_mini_controller/map.py | 24 + .../sphero_mini_controller/maperstellen.py | 0 .../my_first_node copy.py | 183 +++++ .../sphero_mini_controller/my_first_node.py | 766 ++++++++++++++++++ .../my_first_node_alt.py | 323 ++++++++ .../my_first_node_komisch.py | 305 +++++++ .../my_first_node_refwinkel.py | 243 ++++++ .../my_first_node_testfahrt.py | 183 +++++ .../lib/sphero_mini_controller/pfadplanung.py | 223 +++++ .../potential_field_planning (1).py | 199 +++++ .../potential_field_planning.py | 248 ++++++ .../lib/sphero_mini_controller/sphero.py | 22 + .../build/lib/sphero_mini_controller/test.py | 23 + .../lib/sphero_mini_controller/treiber.py | 638 +++++++++++++++ .../lib/sphero_mini_controller/wavefront.py | 154 ++++ .../wavefront_coverage_path_planner.py | 218 +++++ .../sphero_mini_controller/wavefrontgpt2.py | 76 ++ .../sphero_mini_controller/colcon_build.rc | 1 + .../colcon_command_prefix_setup_py.sh | 1 + .../colcon_command_prefix_setup_py.sh.env | 61 ++ .../build/sphero_mini_controller/install.log | 22 + .../__pycache__/sitecustomize.cpython-310.pyc | Bin 0 -> 301 bytes .../prefix_override/sitecustomize.py | 3 + .../sphero_mini_controller.egg-info/PKG-INFO | 12 + .../SOURCES.txt | 20 + .../dependency_links.txt | 1 + .../entry_points.txt | 3 + .../requires.txt | 1 + .../top_level.txt | 1 + .../sphero_mini_controller.egg-info/zip-safe | 1 + ros2_ws/install/.colcon_install_layout | 1 + ros2_ws/install/COLCON_IGNORE | 0 ros2_ws/install/_local_setup_util_ps1.py | 404 +++++++++ ros2_ws/install/_local_setup_util_sh.py | 404 +++++++++ ros2_ws/install/local_setup.bash | 107 +++ ros2_ws/install/local_setup.ps1 | 55 ++ ros2_ws/install/local_setup.sh | 137 ++++ ros2_ws/install/local_setup.zsh | 120 +++ ros2_ws/install/setup.bash | 31 + ros2_ws/install/setup.ps1 | 29 + ros2_ws/install/setup.sh | 45 + ros2_ws/install/setup.zsh | 31 + .../PKG-INFO | 12 + .../SOURCES.txt | 20 + .../dependency_links.txt | 1 + .../entry_points.txt | 3 + .../requires.txt | 1 + .../top_level.txt | 1 + .../zip-safe | 1 + .../sphero_mini_controller/GameWavefront.py | 375 +++++++++ .../WavefrontChatGpt.py | 53 ++ .../WavefrontPlanner.py | 397 +++++++++ .../sphero_mini_controller/__init__.py | 0 .../__pycache__/GameWavefront.cpython-310.pyc | Bin 0 -> 8672 bytes .../WavefrontChatGpt.cpython-310.pyc | Bin 0 -> 2682 bytes .../WavefrontPlanner.cpython-310.pyc | Bin 0 -> 8584 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 208 bytes .../__pycache__/_constants.cpython-310.pyc | Bin 0 -> 2315 bytes .../__pycache__/a_star.cpython-310.pyc | Bin 0 -> 7276 bytes .../__pycache__/blobErkennung.cpython-310.pyc | Bin 0 -> 1182 bytes .../__pycache__/core.cpython-310.pyc | Bin 0 -> 18927 bytes .../dynamicPathPlanning.cpython-310.pyc | Bin 0 -> 12309 bytes .../__pycache__/map.cpython-310.pyc | Bin 0 -> 600 bytes .../__pycache__/maperstellen.cpython-310.pyc | Bin 0 -> 212 bytes .../my_first_node copy.cpython-310.pyc | Bin 0 -> 4396 bytes .../__pycache__/my_first_node.cpython-310.pyc | Bin 0 -> 16367 bytes .../my_first_node_alt.cpython-310.pyc | Bin 0 -> 7256 bytes .../my_first_node_komisch.cpython-310.pyc | Bin 0 -> 6049 bytes .../my_first_node_refwinkel.cpython-310.pyc | Bin 0 -> 5129 bytes .../my_first_node_testfahrt.cpython-310.pyc | Bin 0 -> 4401 bytes .../__pycache__/pfadplanung.cpython-310.pyc | Bin 0 -> 5569 bytes ...tential_field_planning (1).cpython-310.pyc | Bin 0 -> 4688 bytes .../potential_field_planning.cpython-310.pyc | Bin 0 -> 5668 bytes .../__pycache__/simple_moves.cpython-310.pyc | Bin 0 -> 824 bytes .../__pycache__/sphero.cpython-310.pyc | Bin 0 -> 647 bytes .../__pycache__/sphero_main.cpython-310.pyc | Bin 0 -> 3471 bytes .../__pycache__/test.cpython-310.pyc | Bin 0 -> 767 bytes .../__pycache__/treiber.cpython-310.pyc | Bin 0 -> 18883 bytes .../__pycache__/wavefront.cpython-310.pyc | Bin 0 -> 3571 bytes ...ront_coverage_path_planner.cpython-310.pyc | Bin 0 -> 5730 bytes .../__pycache__/wavefrontgpt2.cpython-310.pyc | Bin 0 -> 1823 bytes .../sphero_mini_controller/_constants.py | 72 ++ .../sphero_mini_controller/a_star.py | 282 +++++++ .../sphero_mini_controller/blobErkennung.py | 80 ++ .../sphero_mini_controller/core.py | 638 +++++++++++++++ .../dynamicPathPlanning.py | 504 ++++++++++++ .../sphero_mini_controller/map.py | 24 + .../sphero_mini_controller/maperstellen.py | 0 .../my_first_node copy.py | 183 +++++ .../sphero_mini_controller/my_first_node.py | 766 ++++++++++++++++++ .../my_first_node_alt.py | 323 ++++++++ .../my_first_node_komisch.py | 305 +++++++ .../my_first_node_refwinkel.py | 243 ++++++ .../my_first_node_testfahrt.py | 183 +++++ .../sphero_mini_controller/pfadplanung.py | 223 +++++ .../potential_field_planning (1).py | 199 +++++ .../potential_field_planning.py | 248 ++++++ .../sphero_mini_controller/simple_moves.py | 34 + .../sphero_mini_controller/sphero.py | 22 + .../sphero_mini_controller/sphero_main.py | 137 ++++ .../sphero_mini_controller/test.py | 23 + .../sphero_mini_controller/treiber.py | 638 +++++++++++++++ .../sphero_mini_controller/wavefront.py | 154 ++++ .../wavefront_coverage_path_planner.py | 218 +++++ .../sphero_mini_controller/wavefrontgpt2.py | 76 ++ .../lib/sphero_mini_controller/sphero_mini | 33 + .../lib/sphero_mini_controller/test_node | 33 + .../packages/sphero_mini_controller | 0 .../packages/sphero_mini_controller | 1 + .../hook/ament_prefix_path.dsv | 1 + .../hook/ament_prefix_path.ps1 | 3 + .../hook/ament_prefix_path.sh | 3 + .../hook/pythonpath.dsv | 1 + .../hook/pythonpath.ps1 | 3 + .../sphero_mini_controller/hook/pythonpath.sh | 3 + .../share/sphero_mini_controller/package.bash | 31 + .../share/sphero_mini_controller/package.dsv | 6 + .../share/sphero_mini_controller/package.ps1 | 116 +++ .../share/sphero_mini_controller/package.sh | 87 ++ .../share/sphero_mini_controller/package.xml | 35 + .../share/sphero_mini_controller/package.zsh | 42 + ros2_ws/log/COLCON_IGNORE | 0 .../log/build_2023-07-05_16-11-36/events.log | 38 + .../build_2023-07-05_16-11-36/logger_all.log | 126 +++ .../sphero_mini_controller/command.log | 2 + .../sphero_mini_controller/stderr.log | 0 .../sphero_mini_controller/stdout.log | 22 + .../sphero_mini_controller/stdout_stderr.log | 22 + .../sphero_mini_controller/streams.log | 24 + ros2_ws/log/latest | Bin 0 -> 32 bytes ros2_ws/log/latest_build | Bin 0 -> 58 bytes .../.vscode/c_cpp_properties.json | 20 + .../.vscode/settings.json | 13 + .../src/sphero_mini_controller/package.xml | 35 + .../resource/sphero_conf.json | 3 + .../resource/sphero_mini_controller | 0 ros2_ws/src/sphero_mini_controller/setup.cfg | 4 + ros2_ws/src/sphero_mini_controller/setup.py | 27 + .../.vscode/c_cpp_properties.json | 20 + .../.vscode/settings.json | 12 + .../WavefrontPlanner.py | 389 +++++++++ .../sphero_mini_controller/__init__.py | 0 .../__pycache__/_constants.cpython-310.pyc | Bin 0 -> 2282 bytes .../__pycache__/core.cpython-310.pyc | Bin 0 -> 19045 bytes .../__pycache__/simple_moves.cpython-310.pyc | Bin 0 -> 791 bytes .../sphero_mini_controller/_constants.py | 72 ++ .../sphero_mini_controller/blobErkennung.py | 66 ++ .../sphero_mini_controller/coreROS2.py | 638 +++++++++++++++ .../sphero_mini_controller/karte.jpg | Bin 0 -> 161695 bytes .../sphero_mini_controller/sphero_conf.json | 3 + .../sphero_mini_controller/sphero_mini.py | 756 +++++++++++++++++ .../test/test_copyright.py | 25 + .../test/test_flake8.py | 25 + .../test/test_pep257.py | 23 + 164 files changed, 16285 insertions(+) create mode 100644 ros2_ws/build/.built_by create mode 100644 ros2_ws/build/COLCON_IGNORE create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/GameWavefront.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/WavefrontChatGpt.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/WavefrontPlanner.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/__init__.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/_constants.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/a_star.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/blobErkennung.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/dynamicPathPlanning.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/map.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/maperstellen.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node copy.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_alt.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_komisch.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_refwinkel.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_testfahrt.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/pfadplanung.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/potential_field_planning (1).py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/potential_field_planning.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/sphero.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/test.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/treiber.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefront.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefront_coverage_path_planner.py create mode 100644 ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefrontgpt2.py create mode 100644 ros2_ws/build/sphero_mini_controller/colcon_build.rc create mode 100644 ros2_ws/build/sphero_mini_controller/colcon_command_prefix_setup_py.sh create mode 100644 ros2_ws/build/sphero_mini_controller/colcon_command_prefix_setup_py.sh.env create mode 100644 ros2_ws/build/sphero_mini_controller/install.log create mode 100644 ros2_ws/build/sphero_mini_controller/prefix_override/__pycache__/sitecustomize.cpython-310.pyc create mode 100644 ros2_ws/build/sphero_mini_controller/prefix_override/sitecustomize.py create mode 100644 ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO create mode 100644 ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt create mode 100644 ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt create mode 100644 ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt create mode 100644 ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt create mode 100644 ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt create mode 100644 ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/zip-safe create mode 100644 ros2_ws/install/.colcon_install_layout create mode 100644 ros2_ws/install/COLCON_IGNORE create mode 100644 ros2_ws/install/_local_setup_util_ps1.py create mode 100644 ros2_ws/install/_local_setup_util_sh.py create mode 100644 ros2_ws/install/local_setup.bash create mode 100644 ros2_ws/install/local_setup.ps1 create mode 100644 ros2_ws/install/local_setup.sh create mode 100644 ros2_ws/install/local_setup.zsh create mode 100644 ros2_ws/install/setup.bash create mode 100644 ros2_ws/install/setup.ps1 create mode 100644 ros2_ws/install/setup.sh create mode 100644 ros2_ws/install/setup.zsh create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/PKG-INFO create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/SOURCES.txt create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/dependency_links.txt create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/entry_points.txt create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/requires.txt create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/top_level.txt create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/zip-safe create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/GameWavefront.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/WavefrontChatGpt.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/WavefrontPlanner.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__init__.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/GameWavefront.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/WavefrontChatGpt.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/WavefrontPlanner.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/__init__.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/_constants.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/a_star.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/blobErkennung.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/core.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/dynamicPathPlanning.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/map.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/maperstellen.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node copy.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_alt.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_komisch.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_refwinkel.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_testfahrt.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/pfadplanung.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/potential_field_planning (1).cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/potential_field_planning.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/simple_moves.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/sphero.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/sphero_main.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/test.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/treiber.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/wavefront.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/wavefront_coverage_path_planner.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/wavefrontgpt2.cpython-310.pyc create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/_constants.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/a_star.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/blobErkennung.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/core.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/dynamicPathPlanning.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/map.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/maperstellen.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node copy.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_alt.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_komisch.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_refwinkel.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_testfahrt.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/pfadplanung.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/potential_field_planning (1).py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/potential_field_planning.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/simple_moves.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/sphero.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/sphero_main.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/test.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/treiber.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefront.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefront_coverage_path_planner.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefrontgpt2.py create mode 100644 ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller/sphero_mini create mode 100644 ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller/test_node create mode 100644 ros2_ws/install/sphero_mini_controller/share/ament_index/resource_index/packages/sphero_mini_controller create mode 100644 ros2_ws/install/sphero_mini_controller/share/colcon-core/packages/sphero_mini_controller create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.dsv create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.ps1 create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.sh create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.dsv create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.ps1 create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.sh create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.bash create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.dsv create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.ps1 create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.sh create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.xml create mode 100644 ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.zsh create mode 100644 ros2_ws/log/COLCON_IGNORE create mode 100644 ros2_ws/log/build_2023-07-05_16-11-36/events.log create mode 100644 ros2_ws/log/build_2023-07-05_16-11-36/logger_all.log create mode 100644 ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/command.log create mode 100644 ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stderr.log create mode 100644 ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stdout.log create mode 100644 ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stdout_stderr.log create mode 100644 ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/streams.log create mode 100644 ros2_ws/log/latest create mode 100644 ros2_ws/log/latest_build create mode 100644 ros2_ws/src/sphero_mini_controller/.vscode/c_cpp_properties.json create mode 100644 ros2_ws/src/sphero_mini_controller/.vscode/settings.json create mode 100644 ros2_ws/src/sphero_mini_controller/package.xml create mode 100644 ros2_ws/src/sphero_mini_controller/resource/sphero_conf.json create mode 100644 ros2_ws/src/sphero_mini_controller/resource/sphero_mini_controller create mode 100644 ros2_ws/src/sphero_mini_controller/setup.cfg create mode 100644 ros2_ws/src/sphero_mini_controller/setup.py create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/.vscode/c_cpp_properties.json create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/.vscode/settings.json create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/WavefrontPlanner.py create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__init__.py create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__pycache__/_constants.cpython-310.pyc create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__pycache__/core.cpython-310.pyc create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__pycache__/simple_moves.cpython-310.pyc create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/_constants.py create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/blobErkennung.py create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/coreROS2.py create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/karte.jpg create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/sphero_conf.json create mode 100644 ros2_ws/src/sphero_mini_controller/sphero_mini_controller/sphero_mini.py create mode 100644 ros2_ws/src/sphero_mini_controller/test/test_copyright.py create mode 100644 ros2_ws/src/sphero_mini_controller/test/test_flake8.py create mode 100644 ros2_ws/src/sphero_mini_controller/test/test_pep257.py diff --git a/ros2_ws/build/.built_by b/ros2_ws/build/.built_by new file mode 100644 index 0000000..06e74ac --- /dev/null +++ b/ros2_ws/build/.built_by @@ -0,0 +1 @@ +colcon diff --git a/ros2_ws/build/COLCON_IGNORE b/ros2_ws/build/COLCON_IGNORE new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/GameWavefront.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/GameWavefront.py new file mode 100644 index 0000000..aae934f --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/GameWavefront.py @@ -0,0 +1,375 @@ +# Wavefront Planning +# Sajad Saeedi, Andrew Davison 2017 +# Implementation is based on the following reference +# +# H. Choset, K. M. Lynch, S. Hutchinson, G. Kantor, W. Burgard, L. E. Kavraki and S. Thrun, +# Principles of Robot Motion: Theory, Algorithms, and Implementations, +# MIT Press, Boston, 2005. +# http://www.cs.cmu.edu/afs/cs/Web/People/motionplanning/ +# +# From Chapter 4.5 - Wave-Front Planner +# This planner determines a path via gradient descent on the grid starting from the start. +# Essentially, the planner determines the path one pixel at a time. +# The wave-front planner essentially forms a potential function on the grid which has one local minimum and thus is resolution complete. +# The planner also determines the shortest path, but at the cost of coming dangerously close to obstacles. +# The major drawback of this method is that the planner has to search the entire space for a path + +import pygame, os, math, time, random +import numpy as np +try: + import pygame + from pygame import surfarray + from pygame.locals import * +except ImportError: + raise ImportError('Error Importing Pygame/surfarray') + +pygame.init() + +SCALE = 1 + +# set the width and height of the screen +WIDTH = 1500/SCALE +HEIGHT = 1000/SCALE + +# The region we will fill with obstacles +PLAYFIELDCORNERS = (-3.0, -3.0, 3.0, 3.0) + +# Barrier locations +barriers = [] +# barrier contents are (bx, by, visibilitymask) +for i in range(100): + (bx, by) = (random.uniform(PLAYFIELDCORNERS[0], PLAYFIELDCORNERS[2]), random.uniform(PLAYFIELDCORNERS[1], PLAYFIELDCORNERS[3])) + barrier = [bx, by, 0] + barriers.append(barrier) + +BARRIERRADIUS = 0.1 + + +ROBOTRADIUS = 0.15 +W = 2 * ROBOTRADIUS +SAFEDIST = 0.2 +BARRIERINFILATE = ROBOTRADIUS + +MAXVELOCITY = 0.5 #ms^(-1) max speed of each wheel +MAXACCELERATION = 0.5 #ms^(-2) max rate we can change speed of each wheel + +target = (PLAYFIELDCORNERS[2] + 1.0, 0) + +k = 160/SCALE # pixels per metre for graphics +u0 = WIDTH / 2 +v0 = HEIGHT / 2 + +x = PLAYFIELDCORNERS[0] - 0.5 +y = 0.0 +theta = 0.0 + +vL = 0.00 +vR = 0.00 + +size = [int(WIDTH), int(HEIGHT)] +screen = pygame.display.set_mode(size) +black = (20,20,40) + +# This makes the normal mouse pointer invisible in graphics window +pygame.mouse.set_visible(0) + +# time delta +dt = 0.1 + + +def surfdemo_show(array_img, name): + "displays a surface, waits for user to continue" + screen = pygame.display.set_mode(array_img.shape[:2], 0, 32) + surfarray.blit_array(screen, array_img) + pygame.display.flip() + pygame.display.set_caption(name) + while 1: + e = pygame.event.wait() + if e.type == MOUSEBUTTONDOWN: break + if e.type == KEYDOWN: break + + +def predictPosition(vL, vR, x, y, theta, dt): + + # Position update in time dt + + # Special cases + # Straight line motion + if (vL == vR): + xnew = x + vL * dt * math.cos(theta) + ynew = y + vL * dt * math.sin(theta) + thetanew = theta + # Pure rotation motion + elif (vL == -vR): + xnew = x + ynew = y + thetanew = theta + ((vR - vL) * dt / W) + else: + # Rotation and arc angle of general circular motion + R = W / 2.0 * (vR + vL) / (vR - vL) + deltatheta = (vR - vL) * dt / W + xnew = x + R * (math.sin(deltatheta + theta) - math.sin(theta)) + ynew = y - R * (math.cos(deltatheta + theta) - math.cos(theta)) + thetanew = theta + deltatheta + + return (xnew, ynew, thetanew) + + +def drawBarriers(barriers): + for barrier in barriers: + if(barrier[2] == 0): + pygame.draw.circle(screen, (0,20,80), (int(u0 + k * barrier[0]), int(v0 - k * barrier[1])), int(k * BARRIERRADIUS), 0) + else: + pygame.draw.circle(screen, (0,120,255), (int(u0 + k * barrier[0]), int(v0 - k * barrier[1])), int(k * BARRIERRADIUS), 0) + return + +def dialtebarrieres(imgin, barriers): + imgout = imgin + for barrier in barriers: + if(barrier[2] == 0): + center_u = int(u0 + k * barrier[0]) + center_v = int(v0 - k * barrier[1]) + RAD = BARRIERRADIUS+BARRIERINFILATE + radius = int(k * (RAD)) + radius2 = int(k * (BARRIERRADIUS)) + points = [(x,y) for x in range(center_u-radius, center_u+radius) for y in range(center_v-radius, center_v+radius)] + for pt in points: + vu = center_u - pt[0] + vv = center_v - pt[1] + distance = math.sqrt(vv*vv + vu*vu) + if (distance < radius and distance > radius2 and pt[0] < WIDTH-1 and pt[1] < HEIGHT-1): + imgout[pt[0],pt[1],0] = 0 + imgout[pt[0],pt[1],1] = 40 + imgout[pt[0],pt[1],2] = 80 + return imgout + +def MakeWaveFront(imgarray, start_uv, target_uv): + print ("building wavefront, please wait ... ") + heap = [] + newheap = [] + + u = target_uv[0] + v = target_uv[1] + + imgarray[:,:,0] = 0 # use channel 0 for planning + imgarray[u, v, 0] = 2 + + lastwave = 3 # start by setting nodes around target to 3 + moves = [(u + 1, v), (u - 1, v), (u, v - 1), (u, v + 1)] + for move in moves: + imgarray[move[0], move[1], 0] = 3 + heap.append(move) + + path = False + max_search = int(np.sqrt(WIDTH*WIDTH + HEIGHT*HEIGHT)) + for currentwave in range(4, max_search): + lastwave = lastwave + 1 + while(heap != []): + position = heap.pop() + (u, v) = position + moves = [(u + 1, v), (u - 1, v), (u, v + 1), (u, v - 1)] + for move in moves: + if(imgarray[move[0], move[1], 2] != 80): + if(imgarray[move[0], move[1], 0] == 0 and imgarray[position[0], position[1], 0] == currentwave - 1 and move[0] < WIDTH-1 and move[1] < HEIGHT-1 and move[0]>2 and move[1]>2): + imgarray[move[0], move[1], 0] = currentwave + newheap.append(move) + if(move == start_uv): + path = True + break + if(path == True): + break + if(path == True): + break + heap = newheap + newheap = [] + return imgarray, currentwave + + +def FindPath(imgarray, start_uv, currentwave): + trajectory = [] + nextpt = start_uv + path = [] + for backwave in range(currentwave-1,2,-1): + path.append(nextpt) + (u,v) = nextpt + values = [] + val = [] + moves = [(u + 1, v), (u - 1, v), (u, v + 1), (u, v - 1), (u + 1, v + 1), (u - 1, v - 1), (u + 1, v - 1), (u - 1, v - 1)] + for move in moves: + val.append(imgarray[move[0], move[1], 0]) + if(imgarray[move[0], move[1], 0] == backwave): + values.append(imgarray[move[0], move[1], 0]) + minimum = min(values) + indices = [i for i, v in enumerate(val) if v == minimum] + nextid = random.choice(indices) + nextpt = moves[nextid] + return path + +def GetControl(x,y,theta, waypoint): + wpu = waypoint[0] + wpv = waypoint[1] + + vt = 0.2 + u = u0 + k * x + v = v0 - k * y + vector = (wpu - u, -wpv + v) + vectorangle = math.atan2(vector[1], vector[0]) + psi = vectorangle - theta + + if(abs(psi) > (2*3.14/180)): + if(psi > 0): + vR = vt/2 + vL = -vt/2 + else: + vR = -vt/2 + vL = vt/2 + else: + wlx = x - (W/2.0) * math.sin(theta) + wly = y + (W/2.0) * math.cos(theta) + ulx = u0 + k * wlx + vlx = v0 - k * wly + dl = math.sqrt((ulx-wpu)*(ulx-wpu) + (vlx-wpv)*(vlx-wpv)) + + wrx = x + (W/2.0) * math.sin(theta) + wry = y - (W/2.0) * math.cos(theta) + urx = u0 + k * wrx + vrx = v0 - k * wry + dr = math.sqrt((urx-wpu)*(urx-wpu) + (vrx-wpv)*(vrx-wpv)) + + vR = 1*(2*vt)/(1+(dl/dr)) + vL = 1*(2*vt - vR) + + return vL, vR + +def AnimateRobot(x,y,theta): + # Draw robot + u = u0 + k * x + v = v0 - k * y + pygame.draw.circle(screen, (255,255,255), (int(u), int(v)), int(k * ROBOTRADIUS), 3) + # Draw wheels + # left wheel centre + wlx = x - (W/2.0) * math.sin(theta) + wly = y + (W/2.0) * math.cos(theta) + ulx = u0 + k * wlx + vlx = v0 - k * wly + WHEELBLOB = 0.04 + pygame.draw.circle(screen, (0,0,255), (int(ulx), int(vlx)), int(k * WHEELBLOB)) + # right wheel centre + wrx = x + (W/2.0) * math.sin(theta) + wry = y - (W/2.0) * math.cos(theta) + urx = u0 + k * wrx + vrx = v0 - k * wry + pygame.draw.circle(screen, (0,0,255), (int(urx), int(vrx)), int(k * WHEELBLOB)) + + time.sleep(dt / 5) + pygame.display.flip() + #time.sleep(0.2) + +def AnimateNavigation(barriers, waypint, path): + screen.fill(black) + drawBarriers(barriers) + + for pt in path: + screen.set_at(pt, (255,255,255)) + + pygame.draw.circle(screen, (255,100,0), target_uv, int(k * ROBOTRADIUS), 0) + pygame.draw.circle(screen, (255,100,0), (int(u0 + k * target[0]), int(v0 - k * target[1])), int(k * ROBOTRADIUS), 0) + pygame.draw.circle(screen, (255,0,255), (int(waypint[0]), int(waypint[1])), int(5)) + +def AnimatePath(imgarray, path, start_uv): + for pt in path: + imgarray[pt[0], pt[1], 0] = 0 + imgarray[pt[0], pt[1], 1] = 255 + imgarray[pt[0], pt[1], 2] = 255 + + #imgarray[:,:,0] /= imgarray[:,:,0].max()/255.0 + scalefactor = imgarray[:,:,0].max()/255.0 + imgarray[:,:,0] = imgarray[:,:,0]/scalefactor + imgarray[:,:,0].astype(int) + imgarray[start_uv[0], start_uv[1], 0] = 0 + imgarray[start_uv[0], start_uv[1], 1] = 255 + imgarray[start_uv[0], start_uv[1], 2] = 255 + + surfdemo_show(imgarray, 'Wavefront Path Planning') + + +def GetWaypoint(x,y,theta, path, waypointIndex, waypointSeperation, target): + reset = False + waypoint = path[waypointIndex] + u = u0 + k * x + v = v0 - k * y + distance_to_wp = math.sqrt((waypoint[0]-u)**2+(waypoint[1]-v)**2) # todo compare distance in metrics + if(distance_to_wp < 3): + waypointIndex = waypointSeperation + waypointIndex + if(waypointIndex > len(path)): + waypointIndex = len(path) - 1 + + return waypoint, reset, waypointIndex + +def IsAtTarget(x,y,target): + disttotarget = math.sqrt((x - target[0])**2 + (y - target[1])**2) + if (disttotarget < 0.04): + return True + else: + return False + + +while(1): + start_uv = (int(u0 + k * x), int(v0 - k * y)) + target_uv = (int(u0 + k * target[0]), int(v0 - k * target[1])) + print ("start is: ", start_uv) + print ("goal is: ", target_uv) + + # prepare map of the world for plannign + screen.fill(black) + drawBarriers(barriers) + pygame.draw.circle(screen, (255,100,0), target_uv, int(k * ROBOTRADIUS), 0) + pygame.draw.circle(screen, (255,255,0), start_uv, int(k * ROBOTRADIUS), 0) + imgscreen8 = pygame.surfarray.array3d(screen) + imgscreen16 = np.array(imgscreen8, dtype=np.uint16) + drawBarriers(barriers) + imgarray = dialtebarrieres(imgscreen16, barriers) + pygame.display.flip() + pygame.display.set_caption('Wavefront Path Planning') + + # build wavefront, given the map, start point, and target point + imgarray, currentwave = MakeWaveFront(imgarray, start_uv, target_uv) + print ("press a key to start navaigation ... ") + + # find the path using the wavefront + path = FindPath(imgarray, start_uv, currentwave) + + # normlaize and show the path on the map + AnimatePath(imgarray, path, start_uv) + + # set start point + x = PLAYFIELDCORNERS[0] - 0.5 + y = 0 + theta = 0 + waypointSeperation = 40; + waypointIndex = waypointSeperation; + + # loop to navigate the path from start to target + while(1): + # get a waypoint to follow the path + (waypoint, reset, waypointIndex)= GetWaypoint(x,y,theta, path, waypointIndex, waypointSeperation, target) + + # reset the simulation, if robot is at target + if (IsAtTarget(x,y,target)): + target = (target[0], random.uniform(PLAYFIELDCORNERS[1], PLAYFIELDCORNERS[3])) + x = PLAYFIELDCORNERS[0] - 0.5 + y = 0.0 + theta = 0.0 + break + + + # calculate control signals to get to the next waypoint + (vL, vR) = GetControl(x,y,theta, waypoint) + + # Actually now move and update position based on chosen vL and vR + (x, y, theta) = predictPosition(vL, vR, x, y, theta, dt) + + # animate + AnimateNavigation(barriers, waypoint, path) + AnimateRobot(x,y,theta) + \ No newline at end of file diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/WavefrontChatGpt.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/WavefrontChatGpt.py new file mode 100644 index 0000000..a358dfa --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/WavefrontChatGpt.py @@ -0,0 +1,53 @@ +import numpy as np +import cv2 +import matplotlib.pyplot as plt +import time + +class WaveFrontPlanner: + __wall = 999 + __goal = 1 + __nothing = '000' + __path = 'PATH' + + def __init__(self, mask_list): + if not isinstance(mask_list, np.ndarray): + raise ValueError("Invalid input type. 'mask_list' must be a numpy array.") + self.__mapOfWorld = np.array([['999' if j > 200 else '000' for j in row] for row in mask_list]) + + def minSurroundingNodeValue(self, x, y): + values = [self.__mapOfWorld[x + 1][y], self.__mapOfWorld[x - 1][y], + self.__mapOfWorld[x][y + 1], self.__mapOfWorld[x][y - 1]] + min_value = min(value for value in values if value != self.__nothing) + min_index = values.index(min_value) + 1 + return min_value, min_index + + def waveFrontAlgorithm(self): + goal_coordinates = np.where(self.__mapOfWorld == self.__goal) + goal_x, goal_y = goal_coordinates[0][0], goal_coordinates[1][0] + queue = [(goal_x, goal_y)] + + while queue: + x, y = queue.pop(0) + + if self.__mapOfWorld[x][y] == self.__nothing: + min_value, min_index = self.minSurroundingNodeValue(x, y) + self.__mapOfWorld[x][y] = min_value + 1 + queue.append((x + 1, y)) # Move down + queue.append((x - 1, y)) # Move up + queue.append((x, y + 1)) # Move right + queue.append((x, y - 1)) # Move left + + self.__mapOfWorld[goal_x][goal_y] = self.__goal + + def visualizeMap(self): + plt.imshow(cv2.flip(self.__mapOfWorld, 0), cmap='hot', interpolation='nearest') + plt.colorbar() + plt.show() + +# Beispielverwendung: +mask_list = np.zeros((200, 200)) +mask_list[50:150, 50:150] = 1 + +planner = WaveFrontPlanner(mask_list) +planner.waveFrontAlgorithm() +planner.visualizeMap() diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/WavefrontPlanner.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/WavefrontPlanner.py new file mode 100644 index 0000000..13cebc9 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/WavefrontPlanner.py @@ -0,0 +1,397 @@ +############################################################################ +# WAVEFRONT ALGORITHM +# Adapted to Python Code By Darin Velarde +# Fri Jan 29 13:56:53 PST 2010 +# from C code from John Palmisano +# (www.societyofrobots.com) +############################################################################ +import cv2 +import numpy as np +import matplotlib.pyplot as plt +import time + +class waveFrontPlanner: + ############################################################################ + # WAVEFRONT ALGORITHM + # Adapted to Python Code By Darin Velarde + # Fri Jan 29 13:56:53 PST 2010 + # from C code from John Palmisano + # (www.societyofrobots.com) + ############################################################################ + + def __init__(self, mapOfWorld, slow=False): + self.__slow = slow + self.__mapOfWorld = mapOfWorld + if str(type(mapOfWorld)).find("numpy") != -1: + #If its a numpy array + self.__height, self.__width = self.__mapOfWorld.shape + else: + self.__height, self.__width = len(self.__mapOfWorld), len(self.__mapOfWorld[0]) + + self.__nothing = 000 + self.__wall = 999 + self.__goal = 1 + self.__path = "PATH" + + self.__finalPath = [] + + #Robot value + self.__robot = 254 + #Robot default Location + self.__robot_x = 0 + self.__robot_y = 0 + + #default goal location + self.__goal_x = 8 + self.__goal_y = 9 + + #temp variables + self.__temp_A = 0 + self.__temp_B = 0 + self.__counter = 0 + self.__steps = 0 #determine how processor intensive the algorithm was + + #when searching for a node with a lower value + self.__minimum_node = 250 + self.__min_node_location = 250 + self.__new_state = 1 + self.__old_state = 1 + self.__reset_min = 250 #above this number is a special (wall or robot) + ########################################################################### + + def setRobotPosition(self, x, y): + """ + Sets the robot's current position + + """ + + self.__robot_x = x + self.__robot_y = y + ########################################################################### + + def setGoalPosition(self, x, y): + """ + Sets the goal position. + + """ + + self.__goal_x = x + self.__goal_y = y + ########################################################################### + + def robotPosition(self): + return (self.__robot_x, self.__robot_y) + ########################################################################### + + def goalPosition(self): + return (self.__goal_x, self.__goal_y) + ########################################################################### + + def run(self, prnt=False): + """ + The entry point for the robot algorithm to use wavefront propagation. + + """ + + path = [] + while self.__mapOfWorld[self.__robot_x][self.__robot_y] != self.__goal: + if self.__steps > 10000: + print ("Cannot find a path.") + return + #find new location to go to + self.__new_state = self.propagateWavefront() + #update location of robot + if self.__new_state == 1: + self.__robot_x -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" % (self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 2: + self.__robot_y += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 3: + self.__robot_x += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 4: + self.__robot_y -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + self.__old_state = self.__new_state + msg = "Found the goal in %i steps:\n" % self.__steps + msg += "mapOfWorld size= %i %i\n\n" % (self.__height, self.__width) + if prnt: + print(msg) + self.printMap() + return path + ########################################################################### + + def propagateWavefront(self, prnt=False): + self.unpropagate() + #Old robot location was deleted, store new robot location in mapOfWorld + self.__mapOfWorld[self.__robot_x][self.__robot_y] = self.__robot + self.__path = self.__robot + #start location to begin scan at goal location + self.__mapOfWorld[self.__goal_x][self.__goal_y] = self.__goal + counter = 0 + while counter < 200: #allows for recycling until robot is found + x = 0 + y = 0 + #time.sleep(0.00001) + #while the mapOfWorld hasnt been fully scanned + while x < self.__height and y < self.__width: + #if this location is a wall or the goal, just ignore it + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal: + #a full trail to the robot has been located, finished! + minLoc = self.minSurroundingNodeValue(x, y) + if minLoc < self.__reset_min and \ + self.__mapOfWorld[x][y] == self.__robot: + if prnt: + print("Finished Wavefront:\n") + self.printMap() + # Tell the robot to move after this return. + return self.__min_node_location + #record a value in to this node + elif self.__minimum_node != self.__reset_min: + #if this isnt here, 'nothing' will go in the location + self.__mapOfWorld[x][y] = self.__minimum_node + 1 + #go to next node and/or row + y += 1 + if y == self.__width and x != self.__height: + x += 1 + y = 0 + #print self.__robot_x, self.__robot_y + if prnt: + print("Sweep #: %i\n" % (counter + 1)) + self.printMap() + self.__steps += 1 + counter += 1 + return 0 + ########################################################################### + + def unpropagate(self): + """ + clears old path to determine new path + stay within boundary + + """ + + for x in range(0, self.__height): + for y in range(0, self.__width): + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal and \ + self.__mapOfWorld[x][y] != self.__path: + #if this location is a wall or goal, just ignore it + self.__mapOfWorld[x][y] = self.__nothing #clear that space + ########################################################################### + + def minSurroundingNodeValue(self, x, y): + """ + this method looks at a node and returns the lowest value around that + node. + + """ + + #reset minimum + self.__minimum_node = self.__reset_min + #down + if x < self.__height -1: + if self.__mapOfWorld[x + 1][y] < self.__minimum_node and \ + self.__mapOfWorld[x + 1][y] != self.__nothing: + #find the lowest number node, and exclude empty nodes (0's) + self.__minimum_node = self.__mapOfWorld[x + 1][y] + self.__min_node_location = 3 + #up + if x > 0: + if self.__mapOfWorld[x-1][y] < self.__minimum_node and \ + self.__mapOfWorld[x-1][y] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x-1][y] + self.__min_node_location = 1 + #right + if y < self.__width -1: + if self.__mapOfWorld[x][y + 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y + 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y + 1] + self.__min_node_location = 2 + #left + if y > 0: + if self.__mapOfWorld[x][y - 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y - 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y-1] + self.__min_node_location = 4 + return self.__minimum_node + ########################################################################### + + def printMap(self): + """ + Prints out the map of this instance of the class. + + """ + + msg = '' + for temp_B in range(0, self.__height): + for temp_A in range(0, self.__width): + if self.__mapOfWorld[temp_B][temp_A] == self.__wall: + msg += "%04s" % "[#]" + elif self.__mapOfWorld[temp_B][temp_A] == self.__robot: + msg += "%04s" % "-" + elif self.__mapOfWorld[temp_B][temp_A] == self.__goal: + msg += "%04s" % "G" + else: + msg += "%04s" % str(self.__mapOfWorld[temp_B][temp_A]) + msg += "\n\n" + msg += "\n\n" + print(msg) + # + if self.__slow == True: + time.sleep(0.05) +############################################################################ + +class mapCreate: + ############################################################################ + + def create_map(self, scale,img): + + # Map erstellen + + # Img Objekt + #cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + #success, img = cap.read() + + #Karte von Bild + #succ, img = cv2.imread(2) + print('Original Dimensions : ',img.shape) + + scale_percent = 100-scale # percent of original size + width = int(img.shape[1] * scale_percent / 100) + height = int(img.shape[0] * scale_percent / 100) + dim = (width, height) + + # resize image + resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA) + print('Resized Dimensions : ',resized.shape) + + gray = cv2.cvtColor(resized, cv2.COLOR_RGB2GRAY) + mask = cv2.inRange(gray, np.array([90]), np.array([255])) + + mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + mask_list= np.ndarray.tolist(mask) + + #Markiere alle leeren Zellen mit 000 und alle Zellen mit Hinderniss mit 999 + for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['999' if j > 200 else '000' for j in mask_list[i]] + + mapOfWorld = np.array(mapOfWorld, dtype = int) + mapOfWorldSized = mapOfWorld.copy() + + e = 3 + #Hindernisse Größer machen + for i in range(0,mapOfWorld.shape[0]): + for j in range(0,mapOfWorld.shape[1]): + for r in range(0,e): + try: + if (mapOfWorldSized[i][j] == 999): + mapOfWorld[i][j+e] = 999 + mapOfWorld[i+e][j] = 999 + mapOfWorld[i-e][j] = 999 + mapOfWorld[i][j-e] = 999 + mapOfWorld[i+e][j+e] = 999 + mapOfWorld[i+e][j-e] = 999 + mapOfWorld[i-e][j+e] = 999 + mapOfWorld[i-e][j-e] = 999 + except Exception: + continue + + return mapOfWorld + ############################################################################ + + def scale_koord(self, sx, sy, gx, gy,scale): + scale_percent = 100-scale # percent of original size + + sx = int(sx*scale_percent/100) + sy = int(sy*scale_percent/100) + gx = int(gx*scale_percent/100) + gy = int(gy*scale_percent/100) + + return sx,sy,gx,gy + ############################################################################ + + def rescale_koord(self, path,scale): + scale_percent = 100-scale # percent of original size100 + + path = np.array(path) + + for i in range(len(path)): + path[i] = path[i]*100/scale_percent + + return path + ############################################################################ + + def calc_trans(self, x, y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans +############################################################################### + +if __name__ == "__main__": + """ + X is vertical, Y is horizontal + + """ + + Map = mapCreate() #Map Objekt erzeugen + + #Verkleinerungsfaktor in % + scale = 85 + + img = cv2.imread("D:/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/karte.jpg", cv2.COLOR_RGB2GRAY) + #cap = cv2.VideoCapture(2) + mapWorld = Map.create_map(scale, img) + height, width = img.shape[:2] + + #Angaben in Weltkoordinaten + sy = 300 #X-Koordinate im Weltkoordinaten System + sx = 200 #Y-Koordinate im Weltkoordinaten System + + gy = 700 #X-Koordinate im Weltkordinaten System + gx = 240 #Y-Koordinate im Weltkoordinaten System + + sx,sy,gx,gy = Map.scale_koord(sx,sy,gx,gy,scale) #Kordinaten Tauschen X,Y + + start = time.time() + planner = waveFrontPlanner(mapWorld) + planner.setGoalPosition(gx,gy) + planner.setRobotPosition(sx,sy) + path = planner.run(False) + end = time.time() + print("Took %f seconds to run wavefront simulation" % (end - start)) + + path = Map.rescale_koord(path, scale) + #print(path) +#%% Plot + #Programm Koordinaten + imgTrans = cv2.transpose(img) # X und Y-Achse im Bild tauschen + imgPlot, ax1 = plt.subplots() + + ax1.set_title('Programm Koordinaten') + ax1.imshow(imgTrans) + ax1.set_xlabel('[px]') + ax1.set_ylabel('[px]') + ax1.scatter(path[:,0], path[:,1], color='r') + + #Bild Koordinaten + imgPlot2, ax2 = plt.subplots() + ax2.set_title('Bild Koordinaten') + ax2.set_xlabel('[px]') + ax2.set_ylabel('[px]') + ax2.imshow(img) + ax2.scatter(path[:,1], path[:,0], color='r') + +#%% Sphero Pfad + pathWeltX, pathWeltY = Map.calc_trans(path[:,1], path[:,0], height) \ No newline at end of file diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/__init__.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/_constants.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/_constants.py new file mode 100644 index 0000000..fa8a0e8 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/_constants.py @@ -0,0 +1,72 @@ +''' +Known Peripheral UUIDs, obtained by querying using the Bluepy module: +===================================================================== +Anti DOS Characteristic <00020005-574f-4f20-5370-6865726f2121> +Battery Level Characteristic <Battery Level> +Peripheral Preferred Connection Parameters Characteristic <Peripheral Preferred Connection Parameters> +API V2 Characteristic <00010002-574f-4f20-5370-6865726f2121> +DFU Control Characteristic <00020002-574f-4f20-5370-6865726f2121> +Name Characteristic <Device Name> +Appearance Characteristic <Appearance> +DFU Info Characteristic <00020004-574f-4f20-5370-6865726f2121> +Service Changed Characteristic <Service Changed> +Unknown1 Characteristic <00020003-574f-4f20-5370-6865726f2121> +Unknown2 Characteristic <00010003-574f-4f20-5370-6865726f2121> + +The rest of the values saved in the dictionaries below, were borrowed from +@igbopie's javacript library, which is available at https://github.com/igbopie/spherov2.js + +''' + +deviceID = {"apiProcessor": 0x10, # 16 + "systemInfo": 0x11, # 17 + "powerInfo": 0x13, # 19 + "driving": 0x16, # 22 + "animatronics": 0x17, # 23 + "sensor": 0x18, # 24 + "something": 0x19, # 25 + "userIO": 0x1a, # 26 + "somethingAPI": 0x1f} # 31 + +SystemInfoCommands = {"mainApplicationVersion": 0x00, # 00 + "bootloaderVersion": 0x01, # 01 + "something": 0x06, # 06 + "something2": 0x13, # 19 + "something6": 0x12, # 18 + "something7": 0x28} # 40 + +sendPacketConstants = {"StartOfPacket": 0x8d, # 141 + "EndOfPacket": 0xd8} # 216 + +userIOCommandIDs = {"allLEDs": 0x0e} # 14 + +flags= {"isResponse": 0x01, # 0x01 + "requestsResponse": 0x02, # 0x02 + "requestsOnlyErrorResponse": 0x04, # 0x04 + "resetsInactivityTimeout": 0x08} # 0x08 + +powerCommandIDs={"deepSleep": 0x00, # 0 + "sleep": 0x01, # 01 + "batteryVoltage": 0x03, # 03 + "wake": 0x0D, # 13 + "something": 0x05, # 05 + "something2": 0x10, # 16 + "something3": 0x04, # 04 + "something4": 0x1E} # 30 + +drivingCommands={"rawMotor": 0x01, # 1 + "resetHeading": 0x06, # 6 + "driveAsSphero": 0x04, # 4 + "driveAsRc": 0x02, # 2 + "driveWithHeading": 0x07, # 7 + "stabilization": 0x0C} # 12 + +sensorCommands={'sensorMask': 0x00, # 00 + 'sensorResponse': 0x02, # 02 + 'configureCollision': 0x11, # 17 + 'collisionDetectedAsync': 0x12, # 18 + 'resetLocator': 0x13, # 19 + 'enableCollisionAsync': 0x14, # 20 + 'sensor1': 0x0F, # 15 + 'sensor2': 0x17, # 23 + 'configureSensorStream': 0x0C} # 12 diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/a_star.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/a_star.py new file mode 100644 index 0000000..6d20350 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/a_star.py @@ -0,0 +1,282 @@ +""" + +A* grid planning + +author: Atsushi Sakai(@Atsushi_twi) + Nikos Kanargias (nkana@tee.gr) + +See Wikipedia article (https://en.wikipedia.org/wiki/A*_search_algorithm) + +""" + +import math + +import matplotlib.pyplot as plt + +show_animation = True + + +class AStarPlanner: + + def __init__(self, ox, oy, resolution, rr): + """ + Initialize grid map for a star planning + + ox: x position list of Obstacles [m] + oy: y position list of Obstacles [m] + resolution: grid resolution [m] + rr: robot radius[m] + """ + + self.resolution = resolution + self.rr = rr + self.min_x, self.min_y = 0, 0 + self.max_x, self.max_y = 0, 0 + self.obstacle_map = None + self.x_width, self.y_width = 0, 0 + self.motion = self.get_motion_model() + self.calc_obstacle_map(ox, oy) + + class Node: + def __init__(self, x, y, cost, parent_index): + self.x = x # index of grid + self.y = y # index of grid + self.cost = cost + self.parent_index = parent_index + + def __str__(self): + return str(self.x) + "," + str(self.y) + "," + str( + self.cost) + "," + str(self.parent_index) + + def planning(self, sx, sy, gx, gy): + """ + A star path search + + input: + s_x: start x position [m] + s_y: start y position [m] + gx: goal x position [m] + gy: goal y position [m] + + output: + rx: x position list of the final path + ry: y position list of the final path + """ + + start_node = self.Node(self.calc_xy_index(sx, self.min_x), + self.calc_xy_index(sy, self.min_y), 0.0, -1) + goal_node = self.Node(self.calc_xy_index(gx, self.min_x), + self.calc_xy_index(gy, self.min_y), 0.0, -1) + + open_set, closed_set = dict(), dict() + open_set[self.calc_grid_index(start_node)] = start_node + + while True: + if len(open_set) == 0: + print("Open set is empty..") + break + + c_id = min( + open_set, + key=lambda o: open_set[o].cost + self.calc_heuristic(goal_node, + open_set[ + o])) + current = open_set[c_id] + + # show graph + if show_animation: # pragma: no cover + plt.plot(self.calc_grid_position(current.x, self.min_x), + self.calc_grid_position(current.y, self.min_y), "xc") + # for stopping simulation with the esc key. + plt.gcf().canvas.mpl_connect('key_release_event', + lambda event: [exit( + 0) if event.key == 'escape' else None]) + if len(closed_set.keys()) % 10 == 0: + plt.pause(0.001) + + if current.x == goal_node.x and current.y == goal_node.y: + print("Find goal") + goal_node.parent_index = current.parent_index + goal_node.cost = current.cost + break + + # Remove the item from the open set + del open_set[c_id] + + # Add it to the closed set + closed_set[c_id] = current + + # expand_grid search grid based on motion model + for i, _ in enumerate(self.motion): + node = self.Node(current.x + self.motion[i][0], + current.y + self.motion[i][1], + current.cost + self.motion[i][2], c_id) + n_id = self.calc_grid_index(node) + + # If the node is not safe, do nothing + if not self.verify_node(node): + continue + + if n_id in closed_set: + continue + + if n_id not in open_set: + open_set[n_id] = node # discovered a new node + else: + if open_set[n_id].cost > node.cost: + # This path is the best until now. record it + open_set[n_id] = node + + rx, ry = self.calc_final_path(goal_node, closed_set) + + return rx, ry + + def calc_final_path(self, goal_node, closed_set): + # generate final course + rx, ry = [self.calc_grid_position(goal_node.x, self.min_x)], [ + self.calc_grid_position(goal_node.y, self.min_y)] + parent_index = goal_node.parent_index + while parent_index != -1: + n = closed_set[parent_index] + rx.append(self.calc_grid_position(n.x, self.min_x)) + ry.append(self.calc_grid_position(n.y, self.min_y)) + parent_index = n.parent_index + + return rx, ry + + @staticmethod + def calc_heuristic(n1, n2): + w = 1.0 # weight of heuristic + d = w * math.hypot(n1.x - n2.x, n1.y - n2.y) + return d + + def calc_grid_position(self, index, min_position): + """ + calc grid position + + :param index: + :param min_position: + :return: + """ + pos = index * self.resolution + min_position + return pos + + def calc_xy_index(self, position, min_pos): + return round((position - min_pos) / self.resolution) + + def calc_grid_index(self, node): + return (node.y - self.min_y) * self.x_width + (node.x - self.min_x) + + def verify_node(self, node): + px = self.calc_grid_position(node.x, self.min_x) + py = self.calc_grid_position(node.y, self.min_y) + + if px < self.min_x: + return False + elif py < self.min_y: + return False + elif px >= self.max_x: + return False + elif py >= self.max_y: + return False + + # collision check + if self.obstacle_map[node.x][node.y]: + return False + + return True + + def calc_obstacle_map(self, ox, oy): + + self.min_x = round(min(ox)) + self.min_y = round(min(oy)) + self.max_x = round(max(ox)) + self.max_y = round(max(oy)) + print("min_x:", self.min_x) + print("min_y:", self.min_y) + print("max_x:", self.max_x) + print("max_y:", self.max_y) + + self.x_width = round((self.max_x - self.min_x) / self.resolution) + self.y_width = round((self.max_y - self.min_y) / self.resolution) + print("x_width:", self.x_width) + print("y_width:", self.y_width) + + # obstacle map generation + self.obstacle_map = [[False for _ in range(self.y_width)] + for _ in range(self.x_width)] + for ix in range(self.x_width): + x = self.calc_grid_position(ix, self.min_x) + for iy in range(self.y_width): + y = self.calc_grid_position(iy, self.min_y) + for iox, ioy in zip(ox, oy): + d = math.hypot(iox - x, ioy - y) + if d <= self.rr: + self.obstacle_map[ix][iy] = True + break + + @staticmethod + def get_motion_model(): + # dx, dy, cost + motion = [[1, 0, 1], + [0, 1, 1], + [-1, 0, 1], + [0, -1, 1], + [-1, -1, math.sqrt(2)], + [-1, 1, math.sqrt(2)], + [1, -1, math.sqrt(2)], + [1, 1, math.sqrt(2)]] + + return motion + + +def main(): + print(__file__ + " start!!") + + # start and goal position + sx = 10.0 # [m] + sy = 10.0 # [m] + gx = 50.0 # [m] + gy = 50.0 # [m] + grid_size = 2.0 # [m] + robot_radius = 1.0 # [m] + + # set obstacle positions + ox, oy = [], [] + for i in range(-10, 60): + ox.append(i) + oy.append(-10.0) + for i in range(-10, 60): + ox.append(60.0) + oy.append(i) + for i in range(-10, 61): + ox.append(i) + oy.append(60.0) + for i in range(-10, 61): + ox.append(-10.0) + oy.append(i) + for i in range(-10, 40): + ox.append(20.0) + oy.append(i) + for i in range(0, 40): + ox.append(40.0) + oy.append(60.0 - i) + + if show_animation: # pragma: no cover + plt.plot(ox, oy, ".k") + plt.plot(sx, sy, "og") + plt.plot(gx, gy, "xb") + plt.grid(True) + plt.axis("equal") + + a_star = AStarPlanner(ox, oy, grid_size, robot_radius) + rx, ry = a_star.planning(sx, sy, gx, gy) + + if show_animation: # pragma: no cover + plt.plot(rx, ry, "-r") + plt.pause(0.001) + plt.show() + + +if __name__ == '__main__': + main() diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/blobErkennung.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/blobErkennung.py new file mode 100644 index 0000000..20036f5 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/blobErkennung.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +# Standard imports +import cv2 +import numpy as np; + +# Img Objekt +#cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") +cap = cv2.VideoCapture(4) + +# Set up the detector with default parameters. +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +params.filterByArea = True +params.minArea = 20 +params.maxArea = 200 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +params.filterByInertia = True +params.minInertiaRatio = 0.5 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) + +success, img = cap.read() +height, width = img.shape[:2] + +def calc_trans(x,y): + yTrans = height - y + xTrans = x + + return xTrans, yTrans + +while True: + + success, img = cap.read() + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + + # Detect blobs. + keypoints = detector.detect(gray) + + #print(keypoints) + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + #s = keyPoint.sizekeyPoints + + # Draw detected blobs as red circles. + # cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures + # the size of the circle corresponds to the size of blob + + im_with_keypoints = cv2.drawKeypoints(gray, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) + + # Show blobs + cv2.imshow("Keypoints", im_with_keypoints) + + if cv2.waitKey(10) & 0xFF == ord('q'): + break \ No newline at end of file diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/dynamicPathPlanning.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/dynamicPathPlanning.py new file mode 100644 index 0000000..6607c85 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/dynamicPathPlanning.py @@ -0,0 +1,504 @@ +import numpy as np +import matplotlib.pyplot as plt +import robotController as rc +import mapGeneration as mg +import random +from controller import Robot + +#======[Scenario Control functions]============================================ + +#This function causes the robot to wait some time steps before starting it's operation +#DO NOT CHANGE THIS FUNCTION OR IT'S CALL IN THE CODE +def waitRandomTimeSteps(turtle, stop=50): + initialPose = np.zeros(7) + initialPose[0:2] = findRobotPosition(turtle) + turtle.setPoseGoal(initialPose) + turtle.drive_goalPose() + nRand = random.randint(1, stop) + for i in range(nRand): + turtle.robot.step(turtle.TIME_STEP) + turtle.update() + +#=========[Example functions for finding objects in the map]=================== + +def findTargetPosition(turtlebot): + mapInd = turtlebot.findInMap_FinalTarget() + if(len(mapInd) != 0): + pos = turtlebot.getPositionFromMapIndex(mapInd[0]) + else: + pos = [] + return np.array(pos, dtype=float).tolist() + +def findRobotPosition(turtlebot): + mapInds = turtlebot.findInMap_Robot() + robotPos = np.zeros((len(mapInds), 2)) + for i in range(len(mapInds)): + pos = turtlebot.getPositionFromMapIndex(mapInds[i]) + robotPos[i, ...] = pos + XYInd = np.average(robotPos, axis=0) + return np.array(XYInd, dtype=float).tolist() + +def findObstaclePositions(turtlebot): + mapInds = turtlebot.findInMap_Obstacles() + obsPos = np.zeros((len(mapInds), 2)) + for i in range(len(mapInds)): + pos = turtlebot.getPositionFromMapIndex(mapInds[i]) + obsPos[i, ...] = pos + return np.array(obsPos, dtype=float).tolist() + +#=========[Framework for own implementations of path tracking and planning]==== + +def checkForPossibleCollisions(turtlebot, turtlePos, collisionCntr): + #Fill this with your collision detection between the planed path and + #moving obstacles + hitBox = 0 + + faktor = 0.1 + + turtlePos1 = [turtlePos[0] + faktor, turtlePos[1] + faktor] + turtlePos2 = [turtlePos[0] + faktor, turtlePos[1] - faktor] + turtlePos3 = [turtlePos[0] - faktor, turtlePos[1] + faktor] + turtlePos4 = [turtlePos[0] - faktor, turtlePos[1] - faktor] + + turtlePos5 = [turtlePos[0] + faktor, turtlePos[1] ] + turtlePos6 = [turtlePos[0] , turtlePos[1] + faktor] + turtlePos7 = [turtlePos[0] - faktor, turtlePos[1] ] + turtlePos8 = [turtlePos[0] , turtlePos[1] - faktor] + + turtlePosBool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos)) + turtlePos1Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos1)) + turtlePos2Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos2)) + turtlePos3Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos3)) + turtlePos4Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos4)) + turtlePos5Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos5)) + turtlePos6Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos6)) + turtlePos7Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos7)) + turtlePos8Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos8)) + + hitBox = turtlePosBool | turtlePos1Bool | turtlePos2Bool | turtlePos3Bool | turtlePos4Bool | turtlePos5Bool | turtlePos6Bool | turtlePos7Bool | turtlePos8Bool + + if(hitBox): + collisionCntr += 1 + return True + + if(~hitBox): + return False + +def staticPathTracking(turtlebot, path, indCntr): + #This delta value determines the precision of the position control during path tracking + #A high value leads towards a larger curve radius at corners in the path, + # which might result in a smoothing of the path and keeping the velocity high. + #A lower value sets a higher precision for the precision control and thus, + # the robot might slow down when reaching a path point, but it stays very + # close to the original path + delta = 0.05 + if(indCntr == (len(path)-1)): + delta = 0.01 + #Control the turtlebot to drive towards the target position + isNearGoal = turtlebot.drive_goalPose(delta=delta) + #If the robot is close to the goal select the next goal position + if (isNearGoal): + indCntr += 1 + i = min(indCntr, len(path)) + if i == len(path): + indCntr = -1 + else: + #print("Aktuelle Position Pfad: ", path[i]) + turtlebot.setPoseGoal(path[i]) + return indCntr + +def planInitialPath(turtle): + + initialObstacles = findObstaclePositions(turtle) + initialPose = findRobotPosition(turtle) + initialFinalTarget = findTargetPosition(turtle) + + plt.scatter(np.array(initialObstacles)[..., 0], np.array(initialObstacles)[..., 1]) + plt.scatter(np.array(initialPose)[..., 0], np.array(initialPose)[..., 1] ) + plt.scatter(np.array(initialFinalTarget)[..., 0], np.array(initialFinalTarget)[..., 1]) + plt.show() + plt.pause(0.001) + + from matplotlib import pyplot as ppl + from matplotlib import cm + import random, sys, math, os.path + from matplotlib.pyplot import imread + + #Implementation of RRT + + MAP_IMG = './karte.png' #Black and white image for a map + MIN_NUM_VERT = 20 # Minimum number of vertex in the graph + MAX_NUM_VERT = 1500 # Maximum number of vertex in the graph + STEP_DISTANCE = 20 # Maximum distance between two vertex + SEED = None # For random numbers + + def rapidlyExploringRandomTree(ax, img, start, goal, seed=None): + hundreds = 100 + seed = random.seed(seed) + #print("Zufallsseed: ", seed) + points = [] + graph = [] + points.append(start) + graph.append((start, [])) + print('Generating and conecting random points') + occupied = True + phaseTwo = False + + # Phase two values (points 5 step distances around the goal point) + minX = max(goal[0] - 5 * STEP_DISTANCE, 0) + maxX = min(goal[0] + 5 * STEP_DISTANCE, len(img[0]) - 1) + minY = max(goal[1] - 5 * STEP_DISTANCE, 0) + maxY = min(goal[1] + 5 * STEP_DISTANCE, len(img) - 1) + + i = 0 + while (goal not in points) and (len(points) < MAX_NUM_VERT): + if (i % 100) == 0: + print(i, 'points randomly generated') + + if (len(points) % hundreds) == 0: + print(len(points), 'vertex generated') + hundreds = hundreds + 100 + + while (occupied): + if phaseTwo and (random.random() > 0.8): + point = [random.randint(minX, maxX), random.randint(minY, maxY)] + else: + point = [random.randint(0, len(img[0]) - 1), random.randint(0, len(img) - 1)] + + if (img[point[1]][point[0]][0] * 255 == 255): + occupied = False + + occupied = True + + nearest = findNearestPoint(points, point) + newPoints = connectPoints(point, nearest, img) + addToGraph(ax, graph, newPoints, point) + newPoints.pop(0) # The first element is already in the points list + points.extend(newPoints) + ppl.draw() + i = i + 1 + + if len(points) >= MIN_NUM_VERT: + if not phaseTwo: + print('Phase Two') + phaseTwo = True + + if phaseTwo: + nearest = findNearestPoint(points, goal) + newPoints = connectPoints(goal, nearest, img) + addToGraph(ax, graph, newPoints, goal) + newPoints.pop(0) + points.extend(newPoints) + ppl.draw() + + if goal in points: + print('Goal found, total vertex in graph:', len(points), 'total random points generated:', i) + + path = searchPath(graph, start, [start]) + + for i in range(len(path) - 1): + ax.plot([path[i][0], path[i + 1][0]], [path[i][1], path[i + 1][1]], color='g', linestyle='-', + linewidth=2) + ppl.draw() + + print('Showing resulting map') + print('Final path:', path) + print('The final path is made from:', len(path), 'connected points') + else: + path = None + print('Reached maximum number of vertex and goal was not found') + print('Total vertex in graph:', len(points), 'total random points generated:', i) + print('Showing resulting map') + + ppl.show() + return path + + def searchPath(graph, point, path): + for i in graph: + if point == i[0]: + p = i + + if p[0] == graph[-1][0]: + return path + + for link in p[1]: + path.append(link) + finalPath = searchPath(graph, link, path) + + if finalPath != None: + return finalPath + else: + path.pop() + + def addToGraph(ax, graph, newPoints, point): + if len(newPoints) > 1: # If there is anything to add to the graph + for p in range(len(newPoints) - 1): + nearest = [nearest for nearest in graph if (nearest[0] == [newPoints[p][0], newPoints[p][1]])] + nearest[0][1].append(newPoints[p + 1]) + graph.append((newPoints[p + 1], [])) + + if not p == 0: + ax.plot(newPoints[p][0], newPoints[p][1], '+k') # First point is already painted + ax.plot([newPoints[p][0], newPoints[p + 1][0]], [newPoints[p][1], newPoints[p + 1][1]], color='k', + linestyle='-', linewidth=1) + + if point in newPoints: + ax.plot(point[0], point[1], '.g') # Last point is green + else: + ax.plot(newPoints[p + 1][0], newPoints[p + 1][1], '+k') # Last point is not green + + def connectPoints(a, b, img): + newPoints = [] + newPoints.append([b[0], b[1]]) + step = [(a[0] - b[0]) / float(STEP_DISTANCE), (a[1] - b[1]) / float(STEP_DISTANCE)] + + # Set small steps to check for walls + pointsNeeded = int(math.floor(max(math.fabs(step[0]), math.fabs(step[1])))) + + if math.fabs(step[0]) > math.fabs(step[1]): + if step[0] >= 0: + step = [1, step[1] / math.fabs(step[0])] + else: + step = [-1, step[1] / math.fabs(step[0])] + + else: + if step[1] >= 0: + step = [step[0] / math.fabs(step[1]), 1] + else: + step = [step[0] / math.fabs(step[1]), -1] + + blocked = False + for i in range(pointsNeeded + 1): # Creates points between graph and solitary point + for j in range(STEP_DISTANCE): # Check if there are walls between points + coordX = round(newPoints[i][0] + step[0] * j) + coordY = round(newPoints[i][1] + step[1] * j) + + if coordX == a[0] and coordY == a[1]: + break + if coordY >= len(img) or coordX >= len(img[0]): + break + if img[int(coordY)][int(coordX)][0] * 255 < 255: + blocked = True + if blocked: + break + + if blocked: + break + if not (coordX == a[0] and coordY == a[1]): + newPoints.append( + [newPoints[i][0] + (step[0] * STEP_DISTANCE), newPoints[i][1] + (step[1] * STEP_DISTANCE)]) + + if not blocked: + newPoints.append([a[0], a[1]]) + return newPoints + + def findNearestPoint(points, point): + best = (sys.maxsize, sys.maxsize, sys.maxsize) + for p in points: + if p == point: + continue + dist = math.sqrt((p[0] - point[0]) ** 2 + (p[1] - point[1]) ** 2) + if dist < best[2]: + best = (p[0], p[1], dist) + return (best[0], best[1]) + + karte = np.ones([80, 80, 3], dtype=int) + + # Koordinatentransformation + initialObstacles = [[int(x[0] * 10), int(x[1] * 10)] for x in initialObstacles] + initialPose = [initialPose[0] * 10, initialPose[1] * 10] + initialFinalTarget = [initialFinalTarget[0] * 10, initialFinalTarget[1] * 10] + + initialObstacles = [[int(x[0] + 40), int(x[1] - 40)] for x in initialObstacles] + initialPose = [initialPose[0] + 40, initialPose[1] - 40] + initialFinalTarget = [initialFinalTarget[0] + 40, initialFinalTarget[1] - 40] + + initialObstacles = [[int(x[0]), int(x[1] * (-1))] for x in initialObstacles] + initialPose = [int(initialPose[0]), int(initialPose[1] * -1)] + initialFinalTarget = [int(initialFinalTarget[0]), int(initialFinalTarget[1] * -1)] + + # Karte Hindernisse einzeichnen + rot = np.array(0, dtype=int) + gruen = np.array(0, dtype=int) + blau = np.array(0, dtype=int) + + for k in initialObstacles: + karte[k[1], k[0], 0] = rot; + karte[k[1], k[0], 1] = gruen; + karte[k[1], k[0], 2] = blau; + + karte[k[1] + 1, k[0] + 0, 0] = rot; + karte[k[1] + 1, k[0] + 0, 1] = gruen; + karte[k[1] + 1, k[0] + 0, 2] = blau; + + karte[k[1] - 0, k[0] + 1, 0] = rot; + karte[k[1] - 0, k[0] + 1, 1] = gruen; + karte[k[1] - 0, k[0] + 1, 2] = blau; + + karte[k[1] + 0, k[0] - 1, 0] = rot; + karte[k[1] + 0, k[0] - 1, 1] = gruen; + karte[k[1] + 0, k[0] - 1, 2] = blau; + + karte[k[1] - 1, k[0] - 0, 0] = rot; + karte[k[1] - 1, k[0] - 0, 1] = gruen; + karte[k[1] - 1, k[0] - 0, 2] = blau; + + #Plotten als PNG speichern + from PIL import Image + im = Image.fromarray((karte * 255).astype(np.uint8)) + im.save('karte.png') + + print('Loading map... with file \'', MAP_IMG, '\'') + img = imread(MAP_IMG) + fig = ppl.gcf() + fig.clf() + ax = fig.add_subplot(1, 1, 1) + ax.imshow(img, cmap=cm.Greys_r) + ax.axis('image') + ppl.draw() + print('Map is', len(img[0]), 'x', len(img)) + start = initialPose + goal = initialFinalTarget + + path = rapidlyExploringRandomTree(ax, img, start, goal, seed=SEED) + + # Ruecktransformation + path1 = [[x[0], x[1] * (-1)] for x in path] + path2 = [[(x[0] - 40), (x[1] + 40)] for x in path1] + path3 = [[(x[0] / 10), (x[1] / 10)] for x in path2] + + path3 = [[(x[0]), x[1], 0, 0, 0, 0, 0] for x in path3] + + print("Umgerechneter Pfad", path3) + + if len(sys.argv) > 2: + print('Only one argument is needed') + elif len(sys.argv) > 1: + if os.path.isfile(sys.argv[1]): + MAP_IMG = sys.argv[1] + else: + print(sys.argv[1], 'is not a file') + + pathIndCntr = 0 + + return path3, pathIndCntr + +#=============[Evaluation functions]=========================================== +collisionCntr = 0 +recalcCntr = 0 +def updateEval_RobotCollisions(detector): + global collisionCntr + collision = np.nan_to_num(detector.getValue(), nan=0) + collisionCntr += int(collision) + return bool(collision) + +#======================================[MAIN Execution]======================== +if __name__ == "__main__": + plotting = False + nPlot = 80 + festgefahren = 0 + #Only increase this value if the simulation is running slowly + TIME_STEP = 64 + + #%%=============[DO NOT CHANGE THIS PART OF THE SCRIPT]================== + loopCntr = 0 # + turtle = rc.turtlebotController(TIME_STEP, + poseGoal_tcs=np.array([-0.863, -2.0, 0.0, 0.0, 0.0, 1.0, 3.14159])) # + detector = turtle.robot.getDevice("collisionDetector") # + detector.enable(TIME_STEP) # + # + #The map will be initialized after the first timestep (Transmission) # + turtle.robot.step(turtle.TIME_STEP) # + turtle.update() # + + #The map will have the dynamic objects after the second timestep # + turtle.robot.step(turtle.TIME_STEP) # + turtle.update() # + ScenarioFinalTarget = findTargetPosition(turtle) # + # + if plotting: + mg.plotting_GM_plotly(turtle.mapGenerator) + #DO NOT CHANGE OR REMOVE THIS FUNCTION CALL AND POSITION # + waitRandomTimeSteps(turtle) # + #===============[From here you can change the script]==================== + #%% + #here you should insert a function, which plans a path; the functions has to return a path and related index + try: + path, pathIndCntr = planInitialPath(turtle) + except TypeError: + print("Erste Pfadplanung nicht erfolgreich, neustart...") + try: + path, pathIndCntr = planInitialPath(turtle) + except TypeError: + print("Zweite Pfadplanung nicht erfolgreich, neustart...") + try: + path, pathIndCntr = planInitialPath(turtle) + except TypeError: + print("Dritte Pfadplanung nicht erfolgreich, neustart...") + exit() + + turtle.setPoseGoal(path[pathIndCntr]) + startTime = turtle.robot.getTime() + + inhibitCntr = 0 + while turtle.robot.step(turtle.TIME_STEP) != -1: + #%%===[DO NOT CHANGE THIS PART OF THE LOOP]========================== + loopCntr += 1 # + turtle.update() + + collision = checkForPossibleCollisions(turtle, findRobotPosition(turtle), collisionCntr) + + if ( (plotting) & (loopCntr % nPlot == 0)): # + mg.plotting_GM_plotly(turtle.mapGenerator) # + #====[From here you can change the loop]============================= + #%% + #Follow the static path towards the index pathIndCntr in the path + inhibitCntr = max(0, inhibitCntr - 1) + + if(collision == False): + pathIndCntr = staticPathTracking(turtle, path, pathIndCntr) + + if(collision == True): + print("Knallt") + turtle.stop() + festgefahren += 1 + + if(festgefahren == 10): + #Berechnen neue Pos + vorherigePos = path[pathIndCntr-1] + neueRichtung = [vorherigePos[0] / 2, vorherigePos[1] / 2, 0, 0, 0, 0, 0] + path.insert(pathIndCntr, neueRichtung) + + #Fahre neue Pos an bis du da bist + #Wenn du da bist festgefahren 0 und hoffentlich keine Collsion mehr + recalcCntr += 1 + inhibitCntr = 15 + festgefahren = 0 + collision = False + + #This is the exit condition. Changing this is not recommended + if(pathIndCntr == -1): + break + #End while + #%% + print("Simulation ended") + print("Timesteps with collisions: ", collisionCntr) + print("Recalculations done: ", recalcCntr) + print("Simulationtime: ", turtle.robot.getTime() - startTime) + print("") + print("Recalculation") + print("Timesteps with collisions: ", collisionCntr) + print("Recalculations done: ", recalcCntr) + print("Simulationtime: ", turtle.robot.getTime() - startTime) + + lastStateObstacles = np.array(findObstaclePositions(turtle)) + lastStateRobotPose = np.array(findRobotPosition(turtle)) + + trackRobot = np.array(turtle.trackRobot) + plt.scatter(lastStateObstacles[..., 0], lastStateObstacles[..., 1]) + plt.scatter(lastStateRobotPose[..., 0], lastStateRobotPose[..., 1]) + + plt.plot(trackRobot[..., 0], trackRobot[..., 1]) + plt.scatter(turtle.poseGoal[0], turtle.poseGoal[1]) + plt.scatter(turtle.mapGenerator.finalTarget[0], turtle.mapGenerator.finalTarget[1]) + plt.show() diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/map.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/map.py new file mode 100644 index 0000000..379645d --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/map.py @@ -0,0 +1,24 @@ +#Map erstellen + +# Standard imports +import cv2 +import numpy as np; + +# Img Objekt +cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + +img = cap.read() +#height, width = img.shape[:2] + +success, img = cap.read() +gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) +mask = cv2.inRange(gray, np.array([90]), np.array([255])) + +mask[mask == 255] = 87 + +mask = np.array(mask, dtype=np.uint8).view('S2').squeeze() + +mask[mask == b'WW'] = 'W' + +print(mask) + diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/maperstellen.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/maperstellen.py new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node copy.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node copy.py new file mode 100644 index 0000000..29f2473 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node copy.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime + + +class MyNode(Node): + + def __init__(self): + super().__init__("sphero_mini") + self.publisher_ = self.create_publisher(String,"Imu",5) + #self.get_logger().info("Hello from ROS2") + + def connect(self): + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + #print(i) + + #self.publisher_.publish(i) + + + +def main(args = None): + + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(2) + + try: + while rclpy.ok(): + + sphero.configureSensorMask( + IMU_yaw=True, + IMU_pitch=True, + IMU_roll=True, + IMU_acc_y=True, + IMU_acc_z=True, + IMU_acc_x=True, + IMU_gyro_x=True, + IMU_gyro_y=True, + #IMU_gyro_z=True + ) + sphero.configureSensorStream() + + sensors_values = node.get_sensors_data(sphero) + #rclpy.logdebug(sensors_values) + node.publish_imu(sensors_values, node) + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn LEDs green + + # Aiming: + sphero.setLEDColor(red = 0, green = 0, blue = 0) # Turn main LED off + sphero.stabilization(False) # Turn off stabilization + sphero.setBackLEDIntensity(255) # turn back LED on + sphero.wait(3) # Non-blocking pau`se + sphero.resetHeading() # Reset heading + sphero.stabilization(True) # Turn on stabilization + sphero.setBackLEDIntensity(0) # Turn back LED off + + # Move around: + sphero.setLEDColor(red = 0, green = 0, blue = 255) # Turn main LED blue + sphero.roll(100, 0) # roll forwards (heading = 0) at speed = 50 + + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop + + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn main LED green + sphero.roll(-100, 0) # Keep facing forwards but roll backwards at speed = 50 + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop + + rate.sleep() + + except KeyboardInterrupt: + pass + + rclpy.shutdown() + thread.join() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node.py new file mode 100644 index 0000000..8906b23 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node.py @@ -0,0 +1,766 @@ +#!/usr/bin/env python3 +############################################################################ +#%% Imports +import rclpy +from rclpy.node import Node +from sensor_msgs.msg import Imu +import threading +from geometry_msgs.msg import Quaternion, Vector3 +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import cv2 +import time +import queue +import matplotlib.pyplot as plt +############################################################################ +#%% Blob Detector / Globale Variablen +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +params.filterByArea = True +params.minArea = 10 +params.maxArea = 200 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +#params.filterByInertia = True +#params.minInertiaRatio = 0.01 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) +############################################################################ +#%% Class Video Capture +# Bufferless VideoCapture +# Quelle ? +class VideoCapture: + def __init__(self, name): + self.cap = cv2.VideoCapture(name) + self.q = queue.Queue() + t = threading.Thread(target=self._reader) + t.daemon = True + t.start() + # read frames as soon as they are available, keeping only most recent one + def _reader(self): + while True: + ret, frame = self.cap.read() + #cv2.imshow("Cap", frame) + + if not ret: + break + if not self.q.empty(): + try: + self.q.get_nowait() # discard previous (unprocessed) frame + except queue.Empty: + pass + self.q.put(frame) + + def read(self): + return self.q.get() +############################################################################ +#%% Class Sphero Node +class MyNode(Node): + def __init__(self): + super().__init__("sphero_mini") + + def connect(self): + #Automatisch MAC-Adresse laden + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + def get_pos(self, cap, height): + #zeitanfang = time.time() + + success = False + + while(success == False): + try: + img = cap.read() + + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + keypoints = detector.detect(gray) + + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + success = True + + xTrans, yTrans = self.calc_trans(x,y, height) + except Exception: + continue + + #print("Blob X: %f Blob Y: %f" %(x,y)) + #zeitende = time.time() + #print("Dauer Programmausführung:",(zeitende-zeitanfang)) + #print("Get_Pos: X,Y:", xTrans,yTrans) + + return xTrans,yTrans + + def calc_offset(self, sphero, cap, height): + sphero.roll(100,0) + sphero.wait(1) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = self.get_pos(cap, height) + print("calc_offset: Ref Punkt x,y", ref) + + sphero.wait(1) + sphero.roll(100,180) + sphero.wait(1) + sphero.roll(0,0) + + startpunkt = self.get_pos(cap, height) + print("calc_offset: Startpunkt x,y", startpunkt) + + ref = np.array(ref) + startpunkt = np.array(startpunkt) + + start_ref = ref-startpunkt + + phi = np.arctan2(start_ref[1],start_ref[0]) + phi = np.degrees(phi) + phi = int(-phi) + + print("Phi ohne alles:", phi) + + phi = self.phi_in_range(phi) + + print("Calc_Offset: ", phi) + + return phi + + def calc_av(self,istPos, sollPos, cap, height): + startPos = istPos + + startPos = np.array(startPos) + sollPos = np.array(sollPos) + + pktSpheroKord = sollPos - startPos + + phi = np.arctan2(pktSpheroKord[1], pktSpheroKord[0]) + phi = np.degrees(phi) + + phiZiel = int(phi) + + phiZiel = self.phi_in_range(phiZiel) + + v = 100 + + #print("Calc_AV: a,v", phiZiel, v) + + return phiZiel, v + + def drive_to(self,sphero, targetPos, aOffset, cap, height): + dmin = 20 + pos = self.get_pos(cap, height) + pos = np.array(pos) + + diff = targetPos - pos + + d = np.linalg.norm(diff, ord = 2) + + ar = [] + ar.append(0) + + i = 1 + + while d > dmin: + a,v = self.calc_av(pos,targetPos, cap, height) + + aR = -aOffset - a #Fallunterscheidung? + ar.append((self.phi_in_range(aR))) + + #Fahrbefehl + if(ar[i] != ar[i-1]): + sphero.roll(v, ar[i]) + sphero.wait(0.05) + else: + sphero.wait(0.05) + + #Aktuelle Pos + pos = self.get_pos(cap, height) + pos = np.array(pos) + + #Abweichung + diff = targetPos - pos + diff = np.array(diff) + d = np.linalg.norm(diff, ord = 2) + + i = i + 1 + + sphero.roll(0,0) + print("Ziel Erreicht") + + return + + def calc_trans(self,x,y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans + + def phi_in_range(self,phi): + while(phi < 0): + phi = phi + 360 + while(phi > 360): + phi = phi - 360 + return phi +############################################################################ +#%% Class Wavefrontplanner +class waveFrontPlanner: + ############################################################################ + # WAVEFRONT ALGORITHM + # Adapted to Python Code By Darin Velarde + # Fri Jan 29 13:56:53 PST 2010 + # from C code from John Palmisano + # (www.societyofrobots.com) + ############################################################################ + + def __init__(self, mapOfWorld, slow=False): + self.__slow = slow + self.__mapOfWorld = mapOfWorld + if str(type(mapOfWorld)).find("numpy") != -1: + #If its a numpy array + self.__height, self.__width = self.__mapOfWorld.shape + else: + self.__height, self.__width = len(self.__mapOfWorld), len(self.__mapOfWorld[0]) + + self.__nothing = 000 + self.__wall = 999 + self.__goal = 1 + self.__path = "PATH" + + self.__finalPath = [] + + #Robot value + self.__robot = 254 + #Robot default Location + self.__robot_x = 0 + self.__robot_y = 0 + + #default goal location + self.__goal_x = 8 + self.__goal_y = 9 + + #temp variables + self.__temp_A = 0 + self.__temp_B = 0 + self.__counter = 0 + self.__steps = 0 #determine how processor intensive the algorithm was + + #when searching for a node with a lower value + self.__minimum_node = 250 + self.__min_node_location = 250 + self.__new_state = 1 + self.__old_state = 1 + self.__reset_min = 250 #above this number is a special (wall or robot) + ########################################################################### + + def setRobotPosition(self, x, y): + """ + Sets the robot's current position + + """ + + self.__robot_x = x + self.__robot_y = y + ########################################################################### + + def setGoalPosition(self, x, y): + """ + Sets the goal position. + + """ + + self.__goal_x = x + self.__goal_y = y + ########################################################################### + + def robotPosition(self): + return (self.__robot_x, self.__robot_y) + ########################################################################### + + def goalPosition(self): + return (self.__goal_x, self.__goal_y) + ########################################################################### + + def run(self, prnt=False): + """ + The entry point for the robot algorithm to use wavefront propagation. + + """ + + path = [] + while self.__mapOfWorld[self.__robot_x][self.__robot_y] != self.__goal: + if self.__steps > 10000: + print ("Cannot find a path.") + return + #find new location to go to + self.__new_state = self.propagateWavefront() + #update location of robot + if self.__new_state == 1: + self.__robot_x -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" % (self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 2: + self.__robot_y += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 3: + self.__robot_x += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 4: + self.__robot_y -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + self.__old_state = self.__new_state + msg = "Found the goal in %i steps:\n" % self.__steps + msg += "mapOfWorld size= %i %i\n\n" % (self.__height, self.__width) + if prnt: + print(msg) + self.printMap() + return path + ########################################################################### + + def propagateWavefront(self, prnt=False): + self.unpropagate() + #Old robot location was deleted, store new robot location in mapOfWorld + self.__mapOfWorld[self.__robot_x][self.__robot_y] = self.__robot + self.__path = self.__robot + #start location to begin scan at goal location + self.__mapOfWorld[self.__goal_x][self.__goal_y] = self.__goal + counter = 0 + while counter < 200: #allows for recycling until robot is found + x = 0 + y = 0 + #time.sleep(0.00001) + #while the mapOfWorld hasnt been fully scanned + while x < self.__height and y < self.__width: + #if this location is a wall or the goal, just ignore it + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal: + #a full trail to the robot has been located, finished! + minLoc = self.minSurroundingNodeValue(x, y) + if minLoc < self.__reset_min and \ + self.__mapOfWorld[x][y] == self.__robot: + if prnt: + print("Finished Wavefront:\n") + self.printMap() + # Tell the robot to move after this return. + return self.__min_node_location + #record a value in to this node + elif self.__minimum_node != self.__reset_min: + #if this isnt here, 'nothing' will go in the location + self.__mapOfWorld[x][y] = self.__minimum_node + 1 + #go to next node and/or row + y += 1 + if y == self.__width and x != self.__height: + x += 1 + y = 0 + #print self.__robot_x, self.__robot_y + if prnt: + print("Sweep #: %i\n" % (counter + 1)) + self.printMap() + self.__steps += 1 + counter += 1 + return 0 + ########################################################################### + + def unpropagate(self): + """ + clears old path to determine new path + stay within boundary + + """ + + for x in range(0, self.__height): + for y in range(0, self.__width): + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal and \ + self.__mapOfWorld[x][y] != self.__path: + #if this location is a wall or goal, just ignore it + self.__mapOfWorld[x][y] = self.__nothing #clear that space + ########################################################################### + + def minSurroundingNodeValue(self, x, y): + """ + this method looks at a node and returns the lowest value around that + node. + + """ + + #reset minimum + self.__minimum_node = self.__reset_min + #down + if x < self.__height -1: + if self.__mapOfWorld[x + 1][y] < self.__minimum_node and \ + self.__mapOfWorld[x + 1][y] != self.__nothing: + #find the lowest number node, and exclude empty nodes (0's) + self.__minimum_node = self.__mapOfWorld[x + 1][y] + self.__min_node_location = 3 + #up + if x > 0: + if self.__mapOfWorld[x-1][y] < self.__minimum_node and \ + self.__mapOfWorld[x-1][y] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x-1][y] + self.__min_node_location = 1 + #right + if y < self.__width -1: + if self.__mapOfWorld[x][y + 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y + 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y + 1] + self.__min_node_location = 2 + #left + if y > 0: + if self.__mapOfWorld[x][y - 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y - 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y-1] + self.__min_node_location = 4 + return self.__minimum_node + ########################################################################### + + def printMap(self): + """ + Prints out the map of this instance of the class. + + """ + + msg = '' + for temp_B in range(0, self.__height): + for temp_A in range(0, self.__width): + if self.__mapOfWorld[temp_B][temp_A] == self.__wall: + msg += "%04s" % "[#]" + elif self.__mapOfWorld[temp_B][temp_A] == self.__robot: + msg += "%04s" % "-" + elif self.__mapOfWorld[temp_B][temp_A] == self.__goal: + msg += "%04s" % "G" + else: + msg += "%04s" % str(self.__mapOfWorld[temp_B][temp_A]) + msg += "\n\n" + msg += "\n\n" + print(msg) + # + if self.__slow == True: + time.sleep(0.05) + + def plotMap(self,img,path): + #Programm Koordinaten + print("Plotten") + + imgTrans = cv2.transpose(img) # X und Y-Achse im Bild tauschen + imgPlot, ax1 = plt.subplots() + + ax1.set_title('Programm Koordinaten') + ax1.imshow(imgTrans) + ax1.set_xlabel('[px]') + ax1.set_ylabel('[px]') + ax1.scatter(path[:,0], path[:,1], color='r') + + #Bild Koordinaten + imgPlot2, ax2 = plt.subplots() + ax2.set_title('Bild Koordinaten') + ax2.set_xlabel('[px]') + ax2.set_ylabel('[px]') + ax2.imshow(img) + ax2.scatter(path[:,1], path[:,0], color='r') + + plt.show() + + return + + def getPathWelt(self,path,height,Mapobj): + + pathWeltX, pathWeltY = Mapobj.calc_trans_welt(path[:,1], path[:,0], height) + pathEckenX = [] + pathEckenY = [] + + for i,px in enumerate(pathWeltX): + if (i < len(pathWeltX)-1) & (i > 0): + if px != pathWeltX[i+1]: + if pathWeltY[i]!=pathWeltY[i-1]: + pathEckenX.append(px) + pathEckenY.append(pathWeltY[i]) + if pathWeltY[i] != pathWeltY[i+1]: + if pathWeltX[i]!=pathWeltX[i-1]: + pathEckenX.append(px) + pathEckenY.append(pathWeltY[i]) + + pathEckenX.append(pathWeltX[-1]) + pathEckenY.append(pathWeltY[-1]) + + + uebergabePath = [] + for i in range(0,len(pathEckenX)): + uebergabePath.append((pathEckenX[i],pathEckenY[i])) + + print("Ecken: ", uebergabePath) + + return uebergabePath + +############################################################################ +#%% Class Map +class mapCreate: + ############################################################################ + + def create_map(self, scale, cap): + + # Map erstellen + + # Img Objekt + #cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + img = cap.read() + + #Karte von Bild + #img = cv2.imread("D:/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/map/map.jpg") + print('Original Dimensions : ',img.shape) + + scale_percent = 100-scale # percent of original size + width = int(img.shape[1] * scale_percent / 100) + height = int(img.shape[0] * scale_percent / 100) + dim = (width, height) + + # resize image + resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA) + print('Resized Dimensions : ',resized.shape) + + gray = cv2.cvtColor(resized, cv2.COLOR_RGB2GRAY) + mask = cv2.inRange(gray, np.array([50]), np.array([255])) + + plt.plot(mask) + + mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + mask_list= np.ndarray.tolist(mask) + + #Markiere alle leeren Zellen mit 000 und alle Zellen mit Hinderniss mit 999 + for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['999' if j > 200 else '000' for j in mask_list[i]] + + mapOfWorld = np.array(mapOfWorld, dtype = int) + mapOfWorldSized = mapOfWorld.copy() + + e = 1 + #Hindernisse Größer machen + for i in range(0,mapOfWorld.shape[0]): + for j in range(0,mapOfWorld.shape[1]): + for r in range(0,e): + try: + if (mapOfWorldSized[i][j] == 999): + mapOfWorld[i][j+e] = 999 + mapOfWorld[i+e][j] = 999 + mapOfWorld[i-e][j] = 999 + mapOfWorld[i][j-e] = 999 + mapOfWorld[i+e][j+e] = 999 + mapOfWorld[i+e][j-e] = 999 + mapOfWorld[i-e][j+e] = 999 + mapOfWorld[i-e][j-e] = 999 + except Exception: + continue + + return mapOfWorld,img + ############################################################################ + + def scale_koord(self, sx, sy, gx, gy,scale): + scale_percent = 100-scale # percent of original size + + sx = int(sx*scale_percent/100) + sy = int(sy*scale_percent/100) + gx = int(gx*scale_percent/100) + gy = int(gy*scale_percent/100) + + return sx,sy,gx,gy + ############################################################################ + + def rescale_koord(self, path,scale): + scale_percent = 100-scale # percent of original size100 + + path = np.array(path) + + for i in range(len(path)): + path[i] = path[i]*100/scale_percent + + return path + ############################################################################ + + def calc_trans_welt(self, x, y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans +############################################################################### +#%% Main Funktion +def main(args = None): + #Verbindung herstellen, Node erstellen + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + #rate = node.create_rate(5) + + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + #cap = VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + cap = VideoCapture(4) + Map = mapCreate() + + img = cap.read() + height, width = img.shape[:2] + + scale = 90 + + sphero.setLEDColor(255,0,0) + sphero.wait(1) + sphero.setBackLEDIntensity(0) + sphero.stabilization(True) + + aOffset = node.calc_offset(sphero, cap, height) + + sphero.wait(1) + + #while(True): + + mapWorld,img = Map.create_map(scale, cap) + height, width = img.shape[:2] + + #Befehle für WaveFrontPlanner/Maperstellung + sy,sx = node.get_pos(cap,height) #Aktuelle Roboter Pos in Welt + sy, sx = node.calc_trans(sy,sx,height) + + gy = int(input("Bitte geben Sie die X-Zielkoordinate im Bild Koordinatensystem ein:")) #X-Koordinate im Weltkoordinaten System + gx = int(input("Bitte geben Sie die Y-Zielkoordinate im Bild Koordinatensystem ein:")) #Y-Koordinate im Weltkordinaten System + + sx,sy,gx,gy = Map.scale_koord(sx,sy,gx,gy,scale) #Kordinaten Tauschen X,Y + + start = time.time() + planner = waveFrontPlanner(mapWorld) + planner.setGoalPosition(gx,gy) + planner.setRobotPosition(sx,sy) + path = planner.run(False) + end = time.time() + print("Took %f seconds to run wavefront simulation" % (end - start)) + + path = Map.rescale_koord(path, scale) + + planner.plotMap(img,path) + + pathWelt = planner.getPathWelt(path,height,Map) + + for p in pathWelt: + node.drive_to(sphero,p,aOffset,cap, height) + sphero.wait(1) + + print("Geschafft") + + #if cv2.waitKey(10) & 0xFF == ord('q'): + # break + + rclpy.shutdown() +############################################################################ +#%% Main +if __name__ == '__main__': + main() +############################################################################ diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_alt.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_alt.py new file mode 100644 index 0000000..3b4ea79 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_alt.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime +import cv2 +import time +import queue, threading, time + +# Set up the detector with default parameters. +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +params.filterByArea = True +params.minArea = 10 +params.maxArea = 200 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +#params.filterByInertia = True +#params.minInertiaRatio = 0.01 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) + +# bufferless VideoCapture +class VideoCapture: + def __init__(self, name): + self.cap = cv2.VideoCapture(name) + self.q = queue.Queue() + t = threading.Thread(target=self._reader) + t.daemon = True + t.start() + # read frames as soon as they are available, keeping only most recent one + def _reader(self): + while True: + ret, frame = self.cap.read() + if not ret: + break + if not self.q.empty(): + try: + self.q.get_nowait() # discard previous (unprocessed) frame + except queue.Empty: + pass + self.q.put(frame) + + def read(self): + return self.q.get() + +class MyNode(Node): + + def __init__(self): + super().__init__("sphero_mini") + + def connect(self): + #Automatisch MAC-Adresse laden + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + def get_pos(self, cap, height): + zeitanfang = time.time() + + img = cap.read() + + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + keypoints = detector.detect(gray) + + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + + #print("Blob X: %f Blob Y: %f" %(x,y)) + + zeitende = time.time() + #print("Dauer Programmausführung:",(zeitende-zeitanfang)) + + xTrans, yTrans = self.calc_trans(x,y, height) + + #print("Get_Pos: X,Y:", xTrans,yTrans) + + return xTrans,yTrans + + def calc_offset(self, sphero, cap, height): + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = self.get_pos(cap, height) + print("calc_offset: Ref Punkt x,y", ref) + + sphero.wait(1) + sphero.roll(100,180) + sphero.wait(2) + sphero.roll(0,0) + + startpunkt = self.get_pos(cap, height) + print("calc_offset: Startpunkt x,y", startpunkt) + + ref = np.array(ref) + startpunkt = np.array(startpunkt) + + start_ref = ref-startpunkt + + phi = np.arctan2(start_ref[1],start_ref[0]) + phi = np.degrees(phi) + phi = int(phi) + + print("Phi ohne alles:", phi) + + if(phi < 0): + phi = phi + 360 + + if(phi > 360): + phi = phi - 360 + + print("Calc_Offset: ", phi) + + return phi + + def calc_av(self, sollPos, cap, height): + + startPos = self.get_pos(cap, height) + + startPos = np.array(startPos) + sollPos = np.array(sollPos) + + pktSpheroKord = sollPos - startPos + + phi = np.arctan2(pktSpheroKord[1], pktSpheroKord[0]) + phi = np.degrees(phi) + + phiZiel = int(phi) + + if(phiZiel < 0): + phiZiel = phiZiel + 360 + + if(phi > 360): + phi = phi - 360 + + v = 100 + + print("Calc_AV: a,v", phiZiel, v) + + return phiZiel, v + + def drive_to(self,sphero, targetPos, aOffset, cap, height): + dmin = 50 + pos = self.get_pos(cap, height) + pos = np.array(pos) + + diff = targetPos - pos + + d = np.linalg.norm(diff, ord = 2) + + while d > dmin: + a,v = self.calc_av(targetPos, cap, height) + ar = aOffset + a #Fallunterscheidung? + + pos = self.get_pos(cap, height) + pos = np.array(pos) + diff = targetPos - pos + diff = np.array(diff) + d = np.linalg.norm(diff, ord = 2) + + sphero.roll(v, ar) + time.sleep(0.1) + + sphero.roll(0,ar) + + return + + def calc_trans(self,x,y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans + +def main(args = None): + #Verbindung herstellen, Node erstellen + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(5) + + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + cap = VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + + img = cap.read() + height, width = img.shape[:2] + + sphero.setLEDColor(255,0,0) + sphero.wait(1) + sphero.setBackLEDIntensity(100) + sphero.stabilization(True) + + aOffset = node.calc_offset(sphero, cap, height) + + sphero.roll(100,0+aOffset) + sphero.wait(2) + sphero.roll(0,0) + + sphero.wait(5) + + targetPos = 117,430 + + node.drive_to(sphero,targetPos,aOffset,cap, height) + + rclpy.shutdown() + +if __name__ == '__main__': + main() + diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_komisch.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_komisch.py new file mode 100644 index 0000000..7c5dc3e --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_komisch.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime +import cv2 +import time + + +# Img Objekt +cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + +# Set up the detector with default parameters. +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +#params.filterByArea = True +#params.minArea = 100 +#params.maxArea = 1000 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +#params.filterByInertia = True +#params.minInertiaRatio = 0.01 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) + +class MyNode(Node): + #Konstrukter Node Objekt + def __init__(self): + super().__init__("sphero_mini") + #self.publisher_ = self.create_publisher(String,"Imu",5) + + def connect(self): + #Automatisch MAC-Adresse laden + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + def positionauslesen(self): + zeitanfang = time.time() + + x = (-1) + y = (-1) + + while x < 0 and y < 0: + + success, img = cap.read() + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + # Detect blobs. + keypoints = detector.detect(gray) + + #print(keypoints) + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + + #print("Blob X: %f Blob Y: %f" %(x,y)) + zeitende = time.time() + print("Dauer Programmausführung:",(zeitende-zeitanfang)) + return (x,y) + + def ref_winkel(self, sphero): + + startpunkt = self.positionauslesen() + + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = self.positionauslesen() + + sphero.wait(0.5) + sphero.roll(100,180) + sphero.wait(2) + sphero.roll(0,0) + + soll = (startpunkt[0]+100,startpunkt[1]) + + soll = np.array(soll) + ref = np.array(ref) + startpunkt = np.array(startpunkt) + + start_ref = ref-startpunkt + start_soll = soll-startpunkt + + y = start_ref[0] * start_soll[1] - start_ref[1] * start_soll[0] + x = start_ref[0] * start_soll[0] + start_ref[1] * start_soll[1] + + phi = np.arctan2(y,x) + phi = np.degrees(phi) + phi = int(phi) + + if(phi < 0 ): + phi = phi + 360 + + return phi + + def fahre_ziel(self, sphero, x,y, phiAbw): + + startPos = self.positionauslesen() + sollPos = (x,y) + + startPos = np.array(startPos) + sollPos = np.array(sollPos) + + pktSpheroKord = sollPos - startPos + + phi = np.arctan2(pktSpheroKord[1], pktSpheroKord[0]) + phi = np.degrees(phi) + + phi = int(phi) + + phiZiel = phiAbw - phi + + if(phiZiel < 0 ): + phiZiel = phiZiel + 360 + + print("Winkel Sphero System ", phi) + + #sollPos = (x,y) + #startPos = self.positionauslesen() + #refPos = (startPos[0]+100,startPos[1]) + + #sollPos = np.array(sollPos) + #refPos = np.array(refPos) + #startPos = np.array(startPos) + + #start_ref = refPos-startPos + #start_soll = sollPos-startPos + + #y = start_ref[0] * start_soll[1] - start_ref[1] * start_soll[0] + #x = start_ref[0] * start_soll[0] + start_ref[1] * start_soll[1] + + #phi = np.arctan2(y,x) + #phi = np.degrees(phi) + #phiZiel = phiAbw - (int(phi) * -1) + + print("Zielwinkel: ", phiZiel) + + while(not(np.allclose(startPos, sollPos, atol = 50))): + + sphero.roll(100, (phiZiel)) + + aktPos = self.positionauslesen() + aktPos = np.array(aktPos) + + time.sleep(0.1) + + print("Ziel erreicht") + + sphero.roll(0, (0)) + + return + + +def main(args = None): + #Verbindung herstellen, Node erstellen + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(5) + + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + sphero.setLEDColor(255,0,0) + sphero.wait(1) + sphero.setBackLEDIntensity(100) + sphero.stabilization(True) + + #Differenzwinkel auslesen + phiAbw = node.ref_winkel(sphero) + print("Abweichender Winkel Initialfahrt:", phiAbw) + + sphero.wait(5) + sphero.roll(100, phiAbw) + + sphero.wait(5) + node.fahre_ziel(sphero, 94,94, phiAbw) + + sphero.wait(5) + node.fahre_ziel(sphero, 134,462, phiAbw) + + sphero.wait(5) + node.fahre_ziel(sphero, 581,372, phiAbw) + + sphero.wait(5) + node.fahre_ziel(sphero, 331,308, phiAbw) + + rclpy.spin() + rclpy.shutdown() + +if __name__ == '__main__': + main() + diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_refwinkel.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_refwinkel.py new file mode 100644 index 0000000..78066a4 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_refwinkel.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime + +class MyNode(Node): + #Konstrukter Node Objekt + def __init__(self): + super().__init__("sphero_mini") + #self.publisher_ = self.create_publisher(String,"Imu",5) + + def connect(self): + #Automatisch MAC-Adresse laden + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + def positionauslesen(self): + # Standard imports + import cv2 + import numpy as np; + + # Img Objekt + cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + + # Set up the detector with default parameters. + detector = cv2.SimpleBlobDetector() + + # Setup SimpleBlobDetector parameters. + params = cv2.SimpleBlobDetector_Params() + + # Change thresholds + #params.minThreshold = 5 + #params.maxThreshold = 500 + + #FIlter by Color + params.filterByColor = True + params.blobColor = 255 + + # Filter by Area. + #params.filterByArea = True + #params.minArea = 100 + #params.maxArea = 1000 + + # Filter by Circularity + #params.filterByCircularity = True + #params.minCircularity = 0.5 + #params.maxCircularity = 1 + + # Filter by Convexity + #params.filterByConvexity = True + #params.minConvexity = 0.7 + #params.maxConvexity = 1 + + # Filter by Inertia + #params.filterByInertia = True + #params.minInertiaRatio = 0.01 + + # Create a detector with the parameters + detector = cv2.SimpleBlobDetector_create(params) + + success, img = cap.read() + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + + # Detect blobs. + keypoints = detector.detect(gray) + + #print(keypoints) + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + #keypointss = keyPoint.sizekeyPoints + + #print("Blob X: %f Blob Y: %f" %(x,y)) + + # Draw detected blobs as red circles. + # cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures + # the size of the circle corresponds to the size of blob + + im_with_keypoints = cv2.drawKeypoints(gray, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) + + # Show blobs + cv2.imshow("Keypoints", im_with_keypoints) + + return (x,y) + + def ref_winkel(self, sphero, x,y): + + startpunkt = self.positionauslesen() + + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = self.positionauslesen() + + #print(ref) + + sphero.wait(0.5) + sphero.roll(100,180) + sphero.wait(2) + sphero.roll(0,0) + + soll = (x,y) + + soll = np.array(soll) + ref = np.array(ref) + startpunkt = np.array(startpunkt) + + start_ref = ref-startpunkt + start_soll = soll-startpunkt + phi = np.arccos(np.dot(start_ref,start_soll) / (np.linalg.norm(start_ref)*np.linalg.norm(start_soll))) + return phi + +def main(args = None): + #Verbindung herstellen, Node erstellen + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(5) + + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + #Simple Moves, Erste Bewegung erkennen + #try:oll = (0,0) + # while rclpy.ok(): + + #sphero.positionauslesen() + #soll = 0,0 + phi = node.ref_winkel(sphero, 0,0) + + phi = np.degrees(phi) + + phi = int(phi) + + print("Abweihender Winkel:", phi) + + sphero.wait(0.5) + sphero.roll(100, (0 + phi)) + sphero.wait(2) + sphero.roll(0,0) + +# except KeyboardInterrupt: + # pass + + rclpy.shutdown() + # thread.join() + +if __name__ == '__main__': + main() + + #!/usr/bin/env python3 + diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_testfahrt.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_testfahrt.py new file mode 100644 index 0000000..29f2473 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node_testfahrt.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime + + +class MyNode(Node): + + def __init__(self): + super().__init__("sphero_mini") + self.publisher_ = self.create_publisher(String,"Imu",5) + #self.get_logger().info("Hello from ROS2") + + def connect(self): + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + #print(i) + + #self.publisher_.publish(i) + + + +def main(args = None): + + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(2) + + try: + while rclpy.ok(): + + sphero.configureSensorMask( + IMU_yaw=True, + IMU_pitch=True, + IMU_roll=True, + IMU_acc_y=True, + IMU_acc_z=True, + IMU_acc_x=True, + IMU_gyro_x=True, + IMU_gyro_y=True, + #IMU_gyro_z=True + ) + sphero.configureSensorStream() + + sensors_values = node.get_sensors_data(sphero) + #rclpy.logdebug(sensors_values) + node.publish_imu(sensors_values, node) + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn LEDs green + + # Aiming: + sphero.setLEDColor(red = 0, green = 0, blue = 0) # Turn main LED off + sphero.stabilization(False) # Turn off stabilization + sphero.setBackLEDIntensity(255) # turn back LED on + sphero.wait(3) # Non-blocking pau`se + sphero.resetHeading() # Reset heading + sphero.stabilization(True) # Turn on stabilization + sphero.setBackLEDIntensity(0) # Turn back LED off + + # Move around: + sphero.setLEDColor(red = 0, green = 0, blue = 255) # Turn main LED blue + sphero.roll(100, 0) # roll forwards (heading = 0) at speed = 50 + + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop + + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn main LED green + sphero.roll(-100, 0) # Keep facing forwards but roll backwards at speed = 50 + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop + + rate.sleep() + + except KeyboardInterrupt: + pass + + rclpy.shutdown() + thread.join() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/pfadplanung.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/pfadplanung.py new file mode 100644 index 0000000..d07a2a0 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/pfadplanung.py @@ -0,0 +1,223 @@ + +def planInitialPath(start, goal): + + from matplotlib import pyplot as ppl + from matplotlib import cm + import random, sys, math, os.path + from matplotlib.pyplot import imread + + #Implementation of RRT + + MAP_IMG = './map.jpg' #Black and white image for a map + MIN_NUM_VERT = 20 # Minimum number of vertex in the graph + MAX_NUM_VERT = 1500 # Maximum number of vertex in the graph + STEP_DISTANCE = 10 # Maximum distance between two vertex + SEED = None # For random numbers + + def rapidlyExploringRandomTree(ax, img, start, goal, seed=None): + hundreds = 100 + seed = random.seed(seed) + #print("Zufallsseed: ", seed) + points = [] + graph = [] + points.append(start) + graph.append((start, [])) + print('Generating and conecting random points') + occupied = True + phaseTwo = False + + # Phase two values (points 5 step distances around the goal point) + minX = max(goal[0] - 5 * STEP_DISTANCE, 0) + maxX = min(goal[0] + 5 * STEP_DISTANCE, len(img[0]) - 1) + minY = max(goal[1] - 5 * STEP_DISTANCE, 0) + maxY = min(goal[1] + 5 * STEP_DISTANCE, len(img) - 1) + + i = 0 + while (goal not in points) and (len(points) < MAX_NUM_VERT): + if (i % 100) == 0: + print(i, 'points randomly generated') + + if (len(points) % hundreds) == 0: + print(len(points), 'vertex generated') + hundreds = hundreds + 100 + + while (occupied): + if phaseTwo and (random.random() > 0.8): + point = [random.randint(minX, maxX), random.randint(minY, maxY)] + else: + point = [random.randint(0, len(img[0]) - 1), random.randint(0, len(img) - 1)] + + if (img[point[1]][point[0]][0] * 255 == 255): + occupied = False + + occupied = True + + nearest = findNearestPoint(points, point) + newPoints = connectPoints(point, nearest, img) + addToGraph(ax, graph, newPoints, point) + newPoints.pop(0) # The first element is already in the points list + points.extend(newPoints) + ppl.draw() + i = i + 1 + + if len(points) >= MIN_NUM_VERT: + if not phaseTwo: + print('Phase Two') + phaseTwo = True + + if phaseTwo: + nearest = findNearestPoint(points, goal) + newPoints = connectPoints(goal, nearest, img) + addToGraph(ax, graph, newPoints, goal) + newPoints.pop(0) + points.extend(newPoints) + ppl.draw() + + if goal in points: + print('Goal found, total vertex in graph:', len(points), 'total random points generated:', i) + + path = searchPath(graph, start, [start]) + + for i in range(len(path) - 1): + ax.plot([path[i][0], path[i + 1][0]], [path[i][1], path[i + 1][1]], color='g', linestyle='-', + linewidth=2) + ppl.draw() + + print('Showing resulting map') + print('Final path:', path) + print('The final path is made from:', len(path), 'connected points') + else: + path = None + print('Reached maximum number of vertex and goal was not found') + print('Total vertex in graph:', len(points), 'total random points generated:', i) + print('Showing resulting map') + + ppl.show() + return path + + def searchPath(graph, point, path): + for i in graph: + if point == i[0]: + p = i + + if p[0] == graph[-1][0]: + return path + + for link in p[1]: + path.append(link) + finalPath = searchPath(graph, link, path) + + if finalPath != None: + return finalPath + else: + path.pop() + + def addToGraph(ax, graph, newPoints, point): + if len(newPoints) > 1: # If there is anything to add to the graph + for p in range(len(newPoints) - 1): + nearest = [nearest for nearest in graph if (nearest[0] == [newPoints[p][0], newPoints[p][1]])] + nearest[0][1].append(newPoints[p + 1]) + graph.append((newPoints[p + 1], [])) + + if not p == 0: + ax.plot(newPoints[p][0], newPoints[p][1], '+k') # First point is already painted + ax.plot([newPoints[p][0], newPoints[p + 1][0]], [newPoints[p][1], newPoints[p + 1][1]], color='k', + linestyle='-', linewidth=1) + + if point in newPoints: + ax.plot(point[0], point[1], '.g') # Last point is green + else: + ax.plot(newPoints[p + 1][0], newPoints[p + 1][1], '+k') # Last point is not green + + def connectPoints(a, b, img): + newPoints = [] + newPoints.append([b[0], b[1]]) + step = [(a[0] - b[0]) / float(STEP_DISTANCE), (a[1] - b[1]) / float(STEP_DISTANCE)] + + # Set small steps to check for walls + pointsNeeded = int(math.floor(max(math.fabs(step[0]), math.fabs(step[1])))) + + if math.fabs(step[0]) > math.fabs(step[1]): + if step[0] >= 0: + step = [1, step[1] / math.fabs(step[0])] + else: + step = [-1, step[1] / math.fabs(step[0])] + + else: + if step[1] >= 0: + step = [step[0] / math.fabs(step[1]), 1] + else: + step = [step[0] / math.fabs(step[1]), -1] + + blocked = False + for i in range(pointsNeeded + 1): # Creates points between graph and solitary point + for j in range(STEP_DISTANCE): # Check if there are walls between points + coordX = round(newPoints[i][0] + step[0] * j) + coordY = round(newPoints[i][1] + step[1] * j) + + if coordX == a[0] and coordY == a[1]: + break + if coordY >= len(img) or coordX >= len(img[0]): + break + if img[int(coordY)][int(coordX)][0] * 255 < 255: + blocked = True + if blocked: + break + + if blocked: + break + if not (coordX == a[0] and coordY == a[1]): + newPoints.append( + [newPoints[i][0] + (step[0] * STEP_DISTANCE), newPoints[i][1] + (step[1] * STEP_DISTANCE)]) + + if not blocked: + newPoints.append([a[0], a[1]]) + return newPoints + + def findNearestPoint(points, point): + best = (sys.maxsize, sys.maxsize, sys.maxsize) + for p in points: + if p == point: + continue + dist = math.sqrt((p[0] - point[0]) ** 2 + (p[1] - point[1]) ** 2) + if dist < best[2]: + best = (p[0], p[1], dist) + return (best[0], best[1]) + + # Standard imports + import cv2 + import numpy as np; + + # Img Objekt + cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + val, frame = cap.read() + inverted_image = np.invert(frame) + cv2.imwrite("map.jpg", inverted_image) + + print('Loading map... with file \'', MAP_IMG, '\'') + img = imread(MAP_IMG) + fig = ppl.gcf() + fig.clf() + ax = fig.add_subplot(1, 1, 1) + ax.imshow(img, cmap=cm.Greys_r) + ax.axis('image') + ppl.draw() + print('Map is', len(img[0]), 'x', len(img)) + + path = rapidlyExploringRandomTree(ax, img, start, goal, seed=SEED) + + if len(sys.argv) > 2: + print('Only one argument is needed') + elif len(sys.argv) > 1: + if os.path.isfile(sys.argv[1]): + MAP_IMG = sys.argv[1] + else: + print(sys.argv[1], 'is not a file') + + pathIndCntr = 0 + + return path, pathIndCntr + +start = (200,200) +ziel = (450, 450) +planInitialPath(start, ziel) \ No newline at end of file diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/potential_field_planning (1).py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/potential_field_planning (1).py new file mode 100644 index 0000000..8f136b5 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/potential_field_planning (1).py @@ -0,0 +1,199 @@ +""" + +Potential Field based path planner + +author: Atsushi Sakai (@Atsushi_twi) + +Ref: +https://www.cs.cmu.edu/~motionplanning/lecture/Chap4-Potential-Field_howie.pdf + +""" + +from collections import deque +import numpy as np +import matplotlib.pyplot as plt + +# Parameters +KP = 5.0 # attractive potential gain +ETA = 100.0 # repulsive potential gain +AREA_WIDTH = 30.0 # potential area width [m] +# the number of previous positions used to check oscillations +OSCILLATIONS_DETECTION_LENGTH = 3 + +show_animation = True + + +def calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy): + minx = min(min(ox), sx, gx) - AREA_WIDTH / 2.0 + miny = min(min(oy), sy, gy) - AREA_WIDTH / 2.0 + maxx = max(max(ox), sx, gx) + AREA_WIDTH / 2.0 + maxy = max(max(oy), sy, gy) + AREA_WIDTH / 2.0 + xw = int(round((maxx - minx) / reso)) + yw = int(round((maxy - miny) / reso)) + + # calc each potential + pmap = [[0.0 for i in range(yw)] for i in range(xw)] + + for ix in range(xw): + x = ix * reso + minx + + for iy in range(yw): + y = iy * reso + miny + ug = calc_attractive_potential(x, y, gx, gy) + uo = calc_repulsive_potential(x, y, ox, oy, rr) + uf = ug + uo + pmap[ix][iy] = uf + + return pmap, minx, miny + + +def calc_attractive_potential(x, y, gx, gy): + return 0.5 * KP * np.hypot(x - gx, y - gy) + + +def calc_repulsive_potential(x, y, ox, oy, rr): + # search nearest obstacle + minid = -1 + dmin = float("inf") + for i, _ in enumerate(ox): + d = np.hypot(x - ox[i], y - oy[i]) + if dmin >= d: + dmin = d + minid = i + + # calc repulsive potential + dq = np.hypot(x - ox[minid], y - oy[minid]) + + if dq <= rr: + if dq <= 0.1: + dq = 0.1 + + return 0.5 * ETA * (1.0 / dq - 1.0 / rr) ** 2 + else: + return 0.0 + + +def get_motion_model(): + # dx, dy + motion = [[1, 0], + [0, 1], + [-1, 0], + [0, -1], + [-1, -1], + [-1, 1], + [1, -1], + [1, 1]] + + return motion + + +def oscillations_detection(previous_ids, ix, iy): + previous_ids.append((ix, iy)) + + if (len(previous_ids) > OSCILLATIONS_DETECTION_LENGTH): + previous_ids.popleft() + + # check if contains any duplicates by copying into a set + previous_ids_set = set() + for index in previous_ids: + if index in previous_ids_set: + return True + else: + previous_ids_set.add(index) + return False + + +def potential_field_planning(sx, sy, gx, gy, ox, oy, reso, rr): + + # calc potential field + pmap, minx, miny = calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy) + + # search path + d = np.hypot(sx - gx, sy - gy) + ix = round((sx - minx) / reso) + iy = round((sy - miny) / reso) + gix = round((gx - minx) / reso) + giy = round((gy - miny) / reso) + + if show_animation: + draw_heatmap(pmap) + # for stopping simulation with the esc key. + plt.gcf().canvas.mpl_connect('key_release_event', + lambda event: [exit(0) if event.key == 'escape' else None]) + plt.plot(ix, iy, "*k") + plt.plot(gix, giy, "*m") + + rx, ry = [sx], [sy] + motion = get_motion_model() + previous_ids = deque() + + while d >= reso: + minp = float("inf") + minix, miniy = -1, -1 + for i, _ in enumerate(motion): + inx = int(ix + motion[i][0]) + iny = int(iy + motion[i][1]) + if inx >= len(pmap) or iny >= len(pmap[0]) or inx < 0 or iny < 0: + p = float("inf") # outside area + print("outside potential!") + else: + p = pmap[inx][iny] + if minp > p: + minp = p + minix = inx + miniy = iny + ix = minix + iy = miniy + xp = ix * reso + minx + yp = iy * reso + miny + d = np.hypot(gx - xp, gy - yp) + rx.append(xp) + ry.append(yp) + + if (oscillations_detection(previous_ids, ix, iy)): + print("Oscillation detected at ({},{})!".format(ix, iy)) + break + + if show_animation: + plt.plot(ix, iy, ".r") + plt.pause(0.01) + + print("Goal!!") + + return rx, ry + + +def draw_heatmap(data): + data = np.array(data).T + plt.pcolor(data, vmax=100.0, cmap=plt.cm.Blues) + + +def main(): + print("potential_field_planning start") + + sx = 0.0 # start x position [m] + sy = 10.0 # start y positon [m] + gx = 30.0 # goal x position [m] + gy = 30.0 # goal y position [m] + grid_size = 0.5 # potential grid size [m] + robot_radius = 5.0 # robot radius [m] + + ox = [15.0, 5.0, 20.0, 25.0] # obstacle x position list [m] + oy = [25.0, 15.0, 26.0, 25.0] # obstacle y position list [m] + + if show_animation: + plt.grid(True) + plt.axis("equal") + + # path generation + _, _ = potential_field_planning( + sx, sy, gx, gy, ox, oy, grid_size, robot_radius) + + if show_animation: + plt.show() + + +if __name__ == '__main__': + print(__file__ + " start!!") + main() + print(__file__ + " Done!!") diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/potential_field_planning.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/potential_field_planning.py new file mode 100644 index 0000000..c28279d --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/potential_field_planning.py @@ -0,0 +1,248 @@ +""" + +Potential Field based path planner + +author: Atsushi Sakai (@Atsushi_twi) + +Ref: +https://www.cs.cmu.edu/~motionplanning/lecture/Chap4-Potential-Field_howie.pdf + +""" + +from collections import deque +import numpy as np +import matplotlib.pyplot as plt + +# Parameters +KP = 10.0 # attractive potential gain +ETA = 300.0 # repulsive potential gain +AREA_WIDTH = 704.0 # potential area width [m] +# the number of previous positions used to check oscillations +OSCILLATIONS_DETECTION_LENGTH = 3 + +show_animation = True + +px = [] +py = [] + +def calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy): + minx = min(min(ox), sx, gx) - AREA_WIDTH / 2.0 + miny = min(min(oy), sy, gy) - AREA_WIDTH / 2.0 + maxx = max(max(ox), sx, gx) + AREA_WIDTH / 2.0 + maxy = max(max(oy), sy, gy) + AREA_WIDTH / 2.0 + xw = int(round((maxx - minx) / reso)) + yw = int(round((maxy - miny) / reso)) + + # calc each potential + pmap = [[0.0 for i in range(yw)] for i in range(xw)] + + for ix in range(xw): + x = ix * reso + minx + + for iy in range(yw): + y = iy * reso + miny + ug = calc_attractive_potential(x, y, gx, gy) + uo = calc_repulsive_potential(x, y, ox, oy, rr) + uf = ug + uo + pmap[ix][iy] = uf + + return pmap, minx, miny + + +def calc_attractive_potential(x, y, gx, gy): + return 0.5 * KP * np.hypot(x - gx, y - gy) + + +def calc_repulsive_potential(x, y, ox, oy, rr): + # search nearest obstacle + minid = -1 + dmin = float("inf") + for i, _ in enumerate(ox): + d = np.hypot(x - ox[i], y - oy[i]) + if dmin >= d: + dmin = d + minid = i + + # calc repulsive potential + dq = np.hypot(x - ox[minid], y - oy[minid]) + + if dq <= rr: + if dq <= 0.1: + dq = 0.1 + + return 0.5 * ETA * (1.0 / dq - 1.0 / rr) ** 2 + else: + return 0.0 + + +def get_motion_model(): + # dx, dy + motion = [[1, 0], + [0, 1], + [-1, 0], + [0, -1], + [-1, -1], + [-1, 1], + [1, -1], + [1, 1]] + + return motion + + +def oscillations_detection(previous_ids, ix, iy): + previous_ids.append((ix, iy)) + + if (len(previous_ids) > OSCILLATIONS_DETECTION_LENGTH): + previous_ids.popleft() + + # check if contains any duplicates by copying into a set + previous_ids_set = set() + for index in previous_ids: + if index in previous_ids_set: + return True + else: + previous_ids_set.add(index) + return False + + +def potential_field_planning(sx, sy, gx, gy, ox, oy, reso, rr): + + # calc potential field + pmap, minx, miny = calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy) + + # search path + d = np.hypot(sx - gx, sy - gy) + ix = round((sx - minx) / reso) + iy = round((sy - miny) / reso) + gix = round((gx - minx) / reso) + giy = round((gy - miny) / reso) + + if show_animation: + draw_heatmap(pmap) + # for stopping simulation with the esc key. + plt.gcf().canvas.mpl_connect('key_release_event', + lambda event: [exit(0) if event.key == 'escape' else None]) + plt.plot(ix, iy, "*k") + plt.plot(gix, giy, "*m") + + rx, ry = [sx], [sy] + motion = get_motion_model() + previous_ids = deque() + + while d >= reso: + minp = float("inf") + minix, miniy = -1, -1 + for i, _ in enumerate(motion): + inx = int(ix + motion[i][0]) + iny = int(iy + motion[i][1]) + if inx >= len(pmap) or iny >= len(pmap[0]) or inx < 0 or iny < 0: + p = float("inf") # outside area + print("outside potential!") + else: + p = pmap[inx][iny] + if minp > p: + minp = p + minix = inx + miniy = iny + ix = minix + iy = miniy + xp = ix * reso + minx + yp = iy * reso + miny + d = np.hypot(gx - xp, gy - yp) + rx.append(xp) + ry.append(yp) + + if (oscillations_detection(previous_ids, ix, iy)): + print("Oscillation detected at ({},{})!".format(ix, iy)) + break + + if show_animation: + px.append(ix) + py.append(iy) + plt.plot(ix, iy, ".r") + plt.pause(0.01) + + print("Goal!!") + + return rx, ry + + +def draw_heatmap(data): + data = np.array(data).T + plt.pcolor(data, vmax=100.0, cmap=plt.cm.Blues) + + +def create_map(): + # Map erstellen + # Standard imports + import cv2 + import numpy as np; + + # Img Objekt + #cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + + #success, img = cap.read() + + gray = cv2.imread("/home/ubuntu/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/map/map.jpg",cv2.IMREAD_GRAYSCALE) + + #gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) + mask = cv2.inRange(gray, np.array([90]), np.array([255])) + + mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + + mask_list= np.ndarray.tolist(mask) + + for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['W' if j > 200 else ' ' for j in mask_list[i]] + + #mapOfWorld[200][200] = 'R' #Start Pos + #mapOfWorld[300][300] = 'G' #Ziel Pos + + return mapOfWorld + + +def create_obsticles(mapOfWorld): + ox = [] + oy = [] + + mapOfWorld = np.array(mapOfWorld) + + for i in range(0,mapOfWorld.shape[0]): + for j in range(0,mapOfWorld.shape[1]): + if mapOfWorld[i][j] == 'W': + ox.append(i) + oy.append(j) + + return ox,oy + +def main(): + print("potential_field_planning start") + + mapOfWorld = create_map() + + sx = 50 # start x position [m] + sy = 400 # start y positon [m] + gx = 600 # goal x position [m] + gy = 100 # goal y position [m] + grid_size = 10 # potential grid size [m] + robot_radius = 10 # robot radius [m] + + ox, oy = create_obsticles(mapOfWorld) + + if show_animation: + plt.grid(True) + plt.axis("equal") + + # path generation + _, _ = potential_field_planning(sx, sy, gx, gy, ox, oy, grid_size, robot_radius) + + if show_animation: + plt.show() + + print("X_Pos", px) + print("Y_Pos", py) + +if __name__ == '__main__': + print(__file__ + " start!!") + main() + print(__file__ + " Done!!") diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/sphero.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/sphero.py new file mode 100644 index 0000000..b5138d1 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/sphero.py @@ -0,0 +1,22 @@ +import numpy as np +def ref_winkel(sphero, soll): + + startpunkt = sphero.positionsauslesen + + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = sphero.positionsauslesen + + sphero.wait(0.5) + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + + start_ref = ref-startpunkt + start_soll = soll-startpunkt + phi = np.arccos(start_ref*start_soll / (np.linalg.norm(start_ref)*np.linalg.norm(start_soll))) + return phi + diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/test.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/test.py new file mode 100644 index 0000000..b5c4cbb --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/test.py @@ -0,0 +1,23 @@ +#Map erstellen +# Standard imports +import cv2 +import numpy as np; + +# Img Objekt +cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + +success, img = cap.read() + +gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) +mask = cv2.inRange(gray, np.array([90]), np.array([255])) + +mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + +mask_list= np.ndarray.tolist(mask) + +for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['W' if j > 200 else ' ' for j in mask_list[i]] + +print(mapOfWorld) + + diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/treiber.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/treiber.py new file mode 100644 index 0000000..ec34350 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/treiber.py @@ -0,0 +1,638 @@ +from bluepy.btle import Peripheral +from bluepy import btle +from sphero_mini_controller._constants import * +import struct +import time +import sys + +class SpheroMini(): + def __init__(self, MACAddr, verbosity = 4, user_delegate = None): + ''' + initialize class instance and then build collect BLE sevices and characteristics. + Also sends text string to Anti-DOS characteristic to prevent returning to sleep, + and initializes notifications (which is what the sphero uses to send data back to + the client). + ''' + self.verbosity = verbosity # 0 = Silent, + # 1 = Connection/disconnection only + # 2 = Init messages + # 3 = Recieved commands + # 4 = Acknowledgements + self.sequence = 1 + self.v_batt = None # will be updated with battery voltage when sphero.getBatteryVoltage() is called + self.firmware_version = [] # will be updated with firware version when sphero.returnMainApplicationVersion() is called + + if self.verbosity > 0: + print("[INFO] Connecting to ", MACAddr) + self.p = Peripheral(MACAddr, "random") #connect + + if self.verbosity > 1: + print("[INIT] Initializing") + + # Subscribe to notifications + self.sphero_delegate = MyDelegate(self, user_delegate) # Pass a reference to this instance when initializing + self.p.setDelegate(self.sphero_delegate) + + if self.verbosity > 1: + print("[INIT] Read all characteristics and descriptors") + # Get characteristics and descriptors + self.API_V2_characteristic = self.p.getCharacteristics(uuid="00010002-574f-4f20-5370-6865726f2121")[0] + self.AntiDOS_characteristic = self.p.getCharacteristics(uuid="00020005-574f-4f20-5370-6865726f2121")[0] + self.DFU_characteristic = self.p.getCharacteristics(uuid="00020002-574f-4f20-5370-6865726f2121")[0] + self.DFU2_characteristic = self.p.getCharacteristics(uuid="00020004-574f-4f20-5370-6865726f2121")[0] + self.API_descriptor = self.API_V2_characteristic.getDescriptors(forUUID=0x2902)[0] + self.DFU_descriptor = self.DFU_characteristic.getDescriptors(forUUID=0x2902)[0] + + # The rest of this sequence was observed during bluetooth sniffing: + # Unlock code: prevent the sphero mini from going to sleep again after 10 seconds + if self.verbosity > 1: + print("[INIT] Writing AntiDOS characteristic unlock code") + self.AntiDOS_characteristic.write("usetheforce...band".encode(), withResponse=True) + + # Enable DFU notifications: + if self.verbosity > 1: + print("[INIT] Configuring DFU descriptor") + self.DFU_descriptor.write(struct.pack('<bb', 0x01, 0x00), withResponse = True) + + # No idea what this is for. Possibly a device ID of sorts? Read request returns '00 00 09 00 0c 00 02 02': + if self.verbosity > 1: + print("[INIT] Reading DFU2 characteristic") + _ = self.DFU2_characteristic.read() + + # Enable API notifications: + if self.verbosity > 1: + print("[INIT] Configuring API dectriptor") + self.API_descriptor.write(struct.pack('<bb', 0x01, 0x00), withResponse = True) + + self.wake() + + # Finished initializing: + if self.verbosity > 1: + print("[INIT] Initialization complete\n") + + def disconnect(self): + if self.verbosity > 0: + print("[INFO] Disconnecting") + + self.p.disconnect() + + def wake(self): + ''' + Bring device out of sleep mode (can only be done if device was in sleep, not deep sleep). + If in deep sleep, the device should be connected to USB power to wake. + ''' + if self.verbosity > 2: + print("[SEND {}] Waking".format(self.sequence)) + + self._send(characteristic=self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=powerCommandIDs["wake"], + payload=[]) # empty payload + + self.getAcknowledgement("Wake") + + def sleep(self, deepSleep=False): + ''' + Put device to sleep or deep sleep (deep sleep needs USB power connected to wake up) + ''' + if deepSleep: + sleepCommID=powerCommandIDs["deepSleep"] + if self.verbosity > 0: + print("[INFO] Going into deep sleep. Connect USB power to wake.") + else: + sleepCommID=powerCommandIDs["sleep"] + self._send(characteristic=self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=sleepCommID, + payload=[]) #empty payload + + def setLEDColor(self, red = None, green = None, blue = None): + ''' + Set device LED color based on RGB vales (each can range between 0 and 0xFF) + ''' + if self.verbosity > 2: + print("[SEND {}] Setting main LED colour to [{}, {}, {}]".format(self.sequence, red, green, blue)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['userIO'], # 0x1a + commID = userIOCommandIDs["allLEDs"], # 0x0e + payload = [0x00, 0x0e, red, green, blue]) + + self.getAcknowledgement("LED/backlight") + + def setBackLEDIntensity(self, brightness=None): + ''' + Set device LED backlight intensity based on 0-255 values + + NOTE: this is not the same as aiming - it only turns on the LED + ''' + if self.verbosity > 2: + print("[SEND {}] Setting backlight intensity to {}".format(self.sequence, brightness)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['userIO'], + commID = userIOCommandIDs["allLEDs"], + payload = [0x00, 0x01, brightness]) + + self.getAcknowledgement("LED/backlight") + + def roll(self, speed=None, heading=None): + ''' + Start to move the Sphero at a given direction and speed. + heading: integer from 0 - 360 (degrees) + speed: Integer from 0 - 255 + + Note: the zero heading should be set at startup with the resetHeading method. Otherwise, it may + seem that the sphero doesn't honor the heading argument + ''' + if self.verbosity > 2: + print("[SEND {}] Rolling with speed {} and heading {}".format(self.sequence, speed, heading)) + + if abs(speed) > 255: + print("WARNING: roll speed parameter outside of allowed range (-255 to +255)") + + if speed < 0: + speed = -1*speed+256 # speed values > 256 in the send packet make the spero go in reverse + + speedH = (speed & 0xFF00) >> 8 + speedL = speed & 0xFF + headingH = (heading & 0xFF00) >> 8 + headingL = heading & 0xFF + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['driving'], + commID = drivingCommands["driveWithHeading"], + payload = [speedL, headingH, headingL, speedH]) + + self.getAcknowledgement("Roll") + + def resetHeading(self): + ''' + Reset the heading zero angle to the current heading (useful during aiming) + Note: in order to manually rotate the sphero, you need to call stabilization(False). + Once the heading has been set, call stabilization(True). + ''' + if self.verbosity > 2: + print("[SEND {}] Resetting heading".format(self.sequence)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['driving'], + commID = drivingCommands["resetHeading"], + payload = []) #empty payload + + self.getAcknowledgement("Heading") + + def returnMainApplicationVersion(self): + ''' + Sends command to return application data in a notification + ''' + if self.verbosity > 2: + print("[SEND {}] Requesting firmware version".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID = deviceID['systemInfo'], + commID = SystemInfoCommands['mainApplicationVersion'], + payload = []) # empty + + self.getAcknowledgement("Firmware") + + def getBatteryVoltage(self): + ''' + Sends command to return battery voltage data in a notification. + Data printed to console screen by the handleNotifications() method in the MyDelegate class. + ''' + if self.verbosity > 2: + print("[SEND {}] Requesting battery voltage".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=powerCommandIDs['batteryVoltage'], + payload=[]) # empty + + self.getAcknowledgement("Battery") + + def stabilization(self, stab = True): + ''' + Sends command to turn on/off the motor stabilization system (required when manually turning/aiming the sphero) + ''' + if stab == True: + if self.verbosity > 2: + print("[SEND {}] Enabling stabilization".format(self.sequence)) + val = 1 + else: + if self.verbosity > 2: + print("[SEND {}] Disabling stabilization".format(self.sequence)) + val = 0 + self._send(self.API_V2_characteristic, + devID=deviceID['driving'], + commID=drivingCommands['stabilization'], + payload=[val]) + + self.getAcknowledgement("Stabilization") + + def wait(self, delay): + ''' + This is a non-blocking delay command. It is similar to time.sleep(), except it allows asynchronous + notification handling to still be performed. + ''' + start = time.time() + while(1): + self.p.waitForNotifications(0.001) + if time.time() - start > delay: + break + + def _send(self, characteristic=None, devID=None, commID=None, payload=[]): + ''' + A generic "send" method, which will be used by other methods to send a command ID, payload and + appropriate checksum to a specified device ID. Mainly useful because payloads are optional, + and can be of varying length, to convert packets to binary, and calculate and send the + checksum. For internal use only. + + Packet structure has the following format (in order): + + - Start byte: always 0x8D + - Flags byte: indicate response required, etc + - Virtual device ID: see _constants.py + - Command ID: see _constants.py + - Sequence number: Seems to be arbitrary. I suspect it is used to match commands to response packets (in which the number is echoed). + - Payload: Could be varying number of bytes (incl. none), depending on the command + - Checksum: See below for calculation + - End byte: always 0xD8 + + ''' + sendBytes = [sendPacketConstants["StartOfPacket"], + sum([flags["resetsInactivityTimeout"], flags["requestsResponse"]]), + devID, + commID, + self.sequence] + payload # concatenate payload list + + self.sequence += 1 # Increment sequence number, ensures we can identify response packets are for this command + if self.sequence > 255: + self.sequence = 0 + + # Compute and append checksum and add EOP byte: + # From Sphero docs: "The [checksum is the] modulo 256 sum of all the bytes + # from the device ID through the end of the data payload, + # bit inverted (1's complement)" + # For the sphero mini, the flag bits must be included too: + checksum = 0 + for num in sendBytes[1:]: + checksum = (checksum + num) & 0xFF # bitwise "and to get modulo 256 sum of appropriate bytes + checksum = 0xff - checksum # bitwise 'not' to invert checksum bits + sendBytes += [checksum, sendPacketConstants["EndOfPacket"]] # concatenate + + # Convert numbers to bytes + output = b"".join([x.to_bytes(1, byteorder='big') for x in sendBytes]) + + #send to specified characteristic: + characteristic.write(output, withResponse = True) + + def getAcknowledgement(self, ack): + #wait up to 10 secs for correct acknowledgement to come in, including sequence number! + start = time.time() + while(1): + self.p.waitForNotifications(1) + if self.sphero_delegate.notification_seq == self.sequence-1: # use one less than sequence, because _send function increments it for next send. + if self.verbosity > 3: + print("[RESP {}] {}".format(self.sequence-1, self.sphero_delegate.notification_ack)) + self.sphero_delegate.clear_notification() + break + elif self.sphero_delegate.notification_seq >= 0: + print("Unexpected ACK. Expected: {}/{}, received: {}/{}".format( + ack, self.sequence, self.sphero_delegate.notification_ack.split()[0], + self.sphero_delegate.notification_seq) + ) + if time.time() > start + 10: + print("Timeout waiting for acknowledgement: {}/{}".format(ack, self.sequence)) + break + +# ======================================================================= +# The following functions are experimental: +# ======================================================================= + + def configureCollisionDetection(self, + xThreshold = 50, + yThreshold = 50, + xSpeed = 50, + ySpeed = 50, + deadTime = 50, # in 10 millisecond increments + method = 0x01, # Must be 0x01 + callback = None): + ''' + Appears to function the same as other Sphero models, however speed settings seem to have no effect. + NOTE: Setting to zero seems to cause bluetooth errors with the Sphero Mini/bluepy library - set to + 255 to make it effectively disabled. + + deadTime disables future collisions for a short period of time to avoid repeat triggering by the same + event. Set in 10ms increments. So if deadTime = 50, that means the delay will be 500ms, or half a second. + + From Sphero docs: + + xThreshold/yThreshold: An 8-bit settable threshold for the X (left/right) and Y (front/back) axes + of Sphero. + + xSpeed/ySpeed: An 8-bit settable speed value for the X and Y axes. This setting is ranged by the + speed, then added to xThreshold, yThreshold to generate the final threshold value. + ''' + + if self.verbosity > 2: + print("[SEND {}] Configuring collision detection".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['configureCollision'], + payload=[method, xThreshold, xSpeed, yThreshold, ySpeed, deadTime]) + + self.collision_detection_callback = callback + + self.getAcknowledgement("Collision") + + def configureSensorStream(self): # Use default values + ''' + Send command to configure sensor stream using default values as found during bluetooth + sniffing of the Sphero Edu app. + + Must be called after calling configureSensorMask() + ''' + bitfield1 = 0b00000000 # Unknown function - needs experimenting + bitfield2 = 0b00000000 # Unknown function - needs experimenting + bitfield3 = 0b00000000 # Unknown function - needs experimenting + bitfield4 = 0b00000000 # Unknown function - needs experimenting + + if self.verbosity > 2: + print("[SEND {}] Configuring sensor stream".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['configureSensorStream'], + payload=[bitfield1, bitfield1, bitfield1, bitfield1]) + + self.getAcknowledgement("Sensor") + + def configureSensorMask(self, + sample_rate_divisor = 0x25, # Must be > 0 + packet_count = 0, + IMU_pitch = False, + IMU_roll = False, + IMU_yaw = False, + IMU_acc_x = False, + IMU_acc_y = False, + IMU_acc_z = False, + IMU_gyro_x = False, + IMU_gyro_y = False, + IMU_gyro_z = False): + + ''' + Send command to configure sensor mask using default values as found during bluetooth + sniffing of the Sphero Edu app. From experimentation, it seems that these are he functions of each: + + Sampling_rate_divisor. Slow data EG: Set to 0x32 to the divide data rate by 50. Setting below 25 (0x19) causes + bluetooth errors + + Packet_count: Select the number of packets to transmit before ending the stream. Set to zero to stream infinitely + + All IMU bool parameters: Toggle transmission of that value on or off (e.g. set IMU_acc_x = True to include the + X-axis accelerometer readings in the sensor stream) + ''' + + # Construct bitfields based on function parameters: + IMU_bitfield1 = (IMU_pitch<<2) + (IMU_roll<<1) + IMU_yaw + IMU_bitfield2 = ((IMU_acc_y<<7) + (IMU_acc_z<<6) + (IMU_acc_x<<5) + \ + (IMU_gyro_y<<4) + (IMU_gyro_x<<2) + (IMU_gyro_z<<2)) + + if self.verbosity > 2: + print("[SEND {}] Configuring sensor mask".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensorMask'], + payload=[0x00, # Unknown param - altering it seems to slow data rate. Possibly averages multiple readings? + sample_rate_divisor, + packet_count, # Packet count: select the number of packets to stop streaming after (zero = infinite) + 0b00, # Unknown param: seems to be another accelerometer bitfield? Z-acc, Y-acc + IMU_bitfield1, + IMU_bitfield2, + 0b00]) # reserved, Position?, Position?, velocity?, velocity?, Y-gyro, timer, reserved + + self.getAcknowledgement("Mask") + + ''' + Since the sensor values arrive as unlabelled lists in the order that they appear in the bitfields above, we need + to create a list of sensors that have been configured.Once we have this list, then in the default_delegate class, + we can get sensor values as attributes of the sphero_mini class. + e.g. print(sphero.IMU_yaw) # displays the current yaw angle + ''' + + # Initialize dictionary with sensor names as keys and their bool values (set by the user) as values: + availableSensors = {"IMU_pitch" : IMU_pitch, + "IMU_roll" : IMU_roll, + "IMU_yaw" : IMU_yaw, + "IMU_acc_y" : IMU_acc_y, + "IMU_acc_z" : IMU_acc_z, + "IMU_acc_x" : IMU_acc_x, + "IMU_gyro_y" : IMU_gyro_y, + "IMU_gyro_x" : IMU_gyro_x, + "IMU_gyro_z" : IMU_gyro_z} + + # Create list of of only sensors that have been "activated" (set as true in the method arguments): + self.configured_sensors = [name for name in availableSensors if availableSensors[name] == True] + + def sensor1(self): # Use default values + ''' + Unknown function. Observed in bluetooth sniffing. + ''' + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensor1'], + payload=[0x01]) + + self.getAcknowledgement("Sensor1") + + def sensor2(self): # Use default values + ''' + Unknown function. Observed in bluetooth sniffing. + ''' + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensor2'], + payload=[0x00]) + + self.getAcknowledgement("Sensor2") + +# ======================================================================= + +class MyDelegate(btle.DefaultDelegate): + + ''' + This class handles notifications (both responses and asynchronous notifications). + + Usage of this class is described in the Bluepy documentation + + ''' + + def __init__(self, sphero_class, user_delegate): + self.sphero_class = sphero_class # for saving sensor values as attributes of sphero class instance + self.user_delegate = user_delegate # to directly notify users of callbacks + btle.DefaultDelegate.__init__(self) + self.clear_notification() + self.notificationPacket = [] + + def clear_notification(self): + self.notification_ack = "DEFAULT ACK" + self.notification_seq = -1 + + def bits_to_num(self, bits): + ''' + This helper function decodes bytes from sensor packets into single precision floats. Encoding follows the + the IEEE-754 standard. + ''' + num = int(bits, 2).to_bytes(len(bits) // 8, byteorder='little') + num = struct.unpack('f', num)[0] + return num + + def handleNotification(self, cHandle, data): + ''' + This method acts as an interrupt service routine. When a notification comes in, this + method is invoked, with the variable 'cHandle' being the handle of the characteristic that + sent the notification, and 'data' being the payload (sent one byte at a time, so the packet + needs to be reconstructed) + + The method keeps appending bytes to the payload packet byte list until end-of-packet byte is + encountered. Note that this is an issue, because 0xD8 could be sent as part of the payload of, + say, the battery voltage notification. In future, a more sophisticated method will be required. + ''' + # Allow the user to intercept and process data first.. + if self.user_delegate != None: + if self.user_delegate.handleNotification(cHandle, data): + return + + #print("Received notification with packet ", str(data)) + + for data_byte in data: # parse each byte separately (sometimes they arrive simultaneously) + + self.notificationPacket.append(data_byte) # Add new byte to packet list + + # If end of packet (need to find a better way to segment the packets): + if data_byte == sendPacketConstants['EndOfPacket']: + # Once full the packet has arrived, parse it: + # Packet structure is similar to the outgoing send packets (see docstring in sphero_mini._send()) + + # Attempt to unpack. Might fail if packet is too badly corrupted + try: + start, flags_bits, devid, commcode, seq, *notification_payload, chsum, end = self.notificationPacket + except ValueError: + print("Warning: notification packet unparseable ", self.notificationPacket) + self.notificationPacket = [] # Discard this packet + return # exit + + # Compute and append checksum and add EOP byte: + # From Sphero docs: "The [checksum is the] modulo 256 sum of all the bytes + # from the device ID through the end of the data payload, + # bit inverted (1's complement)" + # For the sphero mini, the flag bits must be included too: + checksum_bytes = [flags_bits, devid, commcode, seq] + notification_payload + checksum = 0 # init + for num in checksum_bytes: + checksum = (checksum + num) & 0xFF # bitwise "and to get modulo 256 sum of appropriate bytes + checksum = 0xff - checksum # bitwise 'not' to invert checksum bits + if checksum != chsum: # check computed checksum against that recieved in the packet + print("Warning: notification packet checksum failed - ", str(self.notificationPacket)) + self.notificationPacket = [] # Discard this packet + return # exit + + # Check if response packet: + if flags_bits & flags['isResponse']: # it is a response + + # Use device ID and command code to determine which command is being acknowledged: + if devid == deviceID['powerInfo'] and commcode == powerCommandIDs['wake']: + self.notification_ack = "Wake acknowledged" # Acknowledgement after wake command + + elif devid == deviceID['driving'] and commcode == drivingCommands['driveWithHeading']: + self.notification_ack = "Roll command acknowledged" + + elif devid == deviceID['driving'] and commcode == drivingCommands['stabilization']: + self.notification_ack = "Stabilization command acknowledged" + + elif devid == deviceID['userIO'] and commcode == userIOCommandIDs['allLEDs']: + self.notification_ack = "LED/backlight color command acknowledged" + + elif devid == deviceID['driving'] and commcode == drivingCommands["resetHeading"]: + self.notification_ack = "Heading reset command acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["configureCollision"]: + self.notification_ack = "Collision detection configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["configureSensorStream"]: + self.notification_ack = "Sensor stream configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensorMask"]: + self.notification_ack = "Mask configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensor1"]: + self.notification_ack = "Sensor1 acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensor2"]: + self.notification_ack = "Sensor2 acknowledged" + + elif devid == deviceID['powerInfo'] and commcode == powerCommandIDs['batteryVoltage']: + V_batt = notification_payload[2] + notification_payload[1]*256 + notification_payload[0]*65536 + V_batt /= 100 # Notification gives V_batt in 10mV increments. Divide by 100 to get to volts. + self.notification_ack = "Battery voltage:" + str(V_batt) + "v" + self.sphero_class.v_batt = V_batt + + elif devid == deviceID['systemInfo'] and commcode == SystemInfoCommands['mainApplicationVersion']: + version = '.'.join(str(x) for x in notification_payload) + self.notification_ack = "Firmware version: " + version + self.sphero_class.firmware_version = notification_payload + + else: + self.notification_ack = "Unknown acknowledgement" #print(self.notificationPacket) + print(self.notificationPacket, "===================> Unknown ack packet") + + self.notification_seq = seq + + else: # Not a response packet - therefore, asynchronous notification (e.g. collision detection, etc): + + # Collision detection: + if devid == deviceID['sensor'] and commcode == sensorCommands['collisionDetectedAsync']: + # The first four bytes are data that is still un-parsed. the remaining unsaved bytes are always zeros + _, _, _, _, _, _, axis, _, Y_mag, _, X_mag, *_ = notification_payload + if axis == 1: + dir = "Left/right" + else: + dir = 'Forward/back' + print("Collision detected:") + print("\tAxis: ", dir) + print("\tX_mag: ", X_mag) + print("\tY_mag: ", Y_mag) + + if self.sphero_class.collision_detection_callback is not None: + self.notificationPacket = [] # need to clear packet, in case new notification comes in during callback + self.sphero_class.collision_detection_callback() + + # Sensor response: + elif devid == deviceID['sensor'] and commcode == sensorCommands['sensorResponse']: + # Convert to binary, pad bytes with leading zeros: + val = '' + for byte in notification_payload: + val += format(int(bin(byte)[2:], 2), '#010b')[2:] + + # Break into 32-bit chunks + nums = [] + while(len(val) > 0): + num, val = val[:32], val[32:] # Slice off first 16 bits + nums.append(num) + + # convert from raw bits to float: + nums = [self.bits_to_num(num) for num in nums] + + # Set sensor values as class attributes: + for name, value in zip(self.sphero_class.configured_sensors, nums): + print("Setting sensor at .", name, str(value)) + setattr(self.sphero_class, name, value) + + # Unrecognized packet structure: + else: + self.notification_ack = "Unknown asynchronous notification" #print(self.notificationPacket) + print(str(self.notificationPacket) + " ===================> Unknown async packet") + + self.notificationPacket = [] # Start new payload after this byte \ No newline at end of file diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefront.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefront.py new file mode 100644 index 0000000..1da3877 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefront.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +import time +import sys + +def newSearch(mapOfWorld, goal, start): + heap = [] + newheap = [] + x, y = goal + lastwave = 3 + # Start out by marking nodes around G with a 3 + moves = [(x + 1, y), (x - 1, y), (x, y - 1), (x, y + 1)] + + for move in moves: + if(mapOfWorld.positions[move] == ' '): + mapOfWorld.positions[move] = 3 + heap.append(move) + for currentwave in range(4, 10000): + lastwave = lastwave + 1 + while(heap != []): + position = heap.pop() + (x, y) = position + moves = [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)] + #x, y = position + for move in moves: + if(mapOfWorld.positions[move] != 'W'): + if(mapOfWorld.positions[move] == ' ' and mapOfWorld.positions[position] == currentwave - 1): + mapOfWorld.positions[move] = currentwave + newheap.append(move) + if(move == start): + return mapOfWorld, lastwave + + #time.sleep(0.25) + #mapOfWorld.display() + #print heap + if(newheap == []): + print("Goal is unreachable") + return 1 + heap = newheap + newheap = [] + +def printf(format, *args): + sys.stdout.write(format % args) + +class Map(object): + + def __init__(self, xdim, ydim, positions): + self.xdim = xdim + self.ydim = ydim + self.positions = positions + def display(self): + printf(" ") + for i in range(self.ydim): + printf("%3s", str(i)) + print + for x in range(self.xdim): + printf("%2s", str(x)) + for y in range(self.ydim): + printf("%3s", str(self.positions[(x, y)])) + print + # Navigate though the number-populated maze + def nav(self, start, current): + self.pos = start + finished = False + + while(finished == False): # Run this code until we're at the goal + x, y = self.pos + self.positions[self.pos] = 'R' # Set the start on the map (this USUALLY keeps start the same) + # SOUTH NORTH WEST EAST + # v v v v + moves = [(x + 1, y), (x - 1, y), (x, y - 1), (x, y + 1)] # Establish our directions + moveDirections = ["South", "North", "West", "East"] # Create a corresponding list of the cardinal directions + """ We don't want least to be 0, because then nothing would be less than it. + However, in order to make our code more robust, we set it to one of the values, + so that we're comparing least to an actual value instead of an arbitrary number (like 10). + """ + # Do the actual comparing, and give us the least index so we know which move was the least + for w in range(len(moves)): + move = moves[w] + + # If the position has the current wave - 1 in it, move there. + if(self.positions[move] == current - 1): + self.least = self.positions[move] + leastIndex = w + # Or, if the position is the goal, stop the loop + elif(self.positions[move] == 'G'): + finished = True + leastIndex = w + # Decrement the current number so we can look for the next number + current = current - 1 + self.positions[self.pos] = ' ' + print( "Moved " + moveDirections[leastIndex]) + self.pos = moves[leastIndex] # This will be converted to "move robot in x direction" + + #time.sleep(0.25) + #self.display() + # Change the goal position (or wherever we stop) to an "!" to show that we've arrived. + self.positions[self.pos] = '!' + self.display() +# Find the goal, given the map +def findGoal(mapOfWorld): + positions = mapOfWorld.positions + for x in range(mapOfWorld.xdim): + for y in range(mapOfWorld.ydim): + if(mapOfWorld.positions[(x, y)] == 'G'): + return (x, y) +# Find the start, given the map +def findStart(mapOfWorld): + positions = mapOfWorld.positions + for x in range(mapOfWorld.xdim): + for y in range(mapOfWorld.ydim): + if(mapOfWorld.positions[(x, y)] == 'R'): + + return (x, y) + +def convertMap(mapOfWorld): + positions = {} + xdim = len(mapOfWorld) + ydim = len(mapOfWorld[1]) + for y in range(ydim): + for x in range(xdim): + positions[(x, y)] = mapOfWorld[x][y] + + return Map(xdim, ydim, positions) + +# Map erstellen +# Standard imports +import cv2 +import numpy as np; + +# Img Objekt +cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + +success, img = cap.read() + +gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) +mask = cv2.inRange(gray, np.array([90]), np.array([255])) + +mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + +mask_list= np.ndarray.tolist(mask) + +for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['W' if j > 200 else ' ' for j in mask_list[i]] + +mapOfWorld[200][200] = 'R' #Start Pos +mapOfWorld[300][300] = 'G' #Ziel Pos + +print(mapOfWorld[200][200]) +print(mapOfWorld[300][300]) + +mapOfLand = convertMap(mapOfWorld) +mapOfLand.display() +mapOfLand, lastwave = newSearch(mapOfLand, findGoal(mapOfLand), findStart(mapOfLand)) +mapOfLand.nav(findStart(mapOfLand), lastwave) diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefront_coverage_path_planner.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefront_coverage_path_planner.py new file mode 100644 index 0000000..8586140 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefront_coverage_path_planner.py @@ -0,0 +1,218 @@ +""" +Distance/Path Transform Wavefront Coverage Path Planner + +author: Todd Tang +paper: Planning paths of complete coverage of an unstructured environment + by a mobile robot - Zelinsky et.al. +link: http://pinkwink.kr/attachment/cfile3.uf@1354654A4E8945BD13FE77.pdf +""" + +import os +import sys + +import matplotlib.pyplot as plt +import numpy as np +from scipy import ndimage + +do_animation = True + + +def transform( + grid_map, src, distance_type='chessboard', + transform_type='path', alpha=0.01 +): + """transform + + calculating transform of transform_type from src + in given distance_type + + :param grid_map: 2d binary map + :param src: distance transform source + :param distance_type: type of distance used + :param transform_type: type of transform used + :param alpha: weight of Obstacle Transform used when using path_transform + """ + + n_rows, n_cols = grid_map.shape + + if n_rows == 0 or n_cols == 0: + sys.exit('Empty grid_map.') + + inc_order = [[0, 1], [1, 1], [1, 0], [1, -1], + [0, -1], [-1, -1], [-1, 0], [-1, 1]] + if distance_type == 'chessboard': + cost = [1, 1, 1, 1, 1, 1, 1, 1] + elif distance_type == 'eculidean': + cost = [1, np.sqrt(2), 1, np.sqrt(2), 1, np.sqrt(2), 1, np.sqrt(2)] + else: + sys.exit('Unsupported distance type.') + + transform_matrix = float('inf') * np.ones_like(grid_map, dtype=float) + transform_matrix[src[0], src[1]] = 0 + if transform_type == 'distance': + eT = np.zeros_like(grid_map) + elif transform_type == 'path': + eT = ndimage.distance_transform_cdt(1 - grid_map, distance_type) + else: + sys.exit('Unsupported transform type.') + + # set obstacle transform_matrix value to infinity + for i in range(n_rows): + for j in range(n_cols): + if grid_map[i][j] == 1.0: + transform_matrix[i][j] = float('inf') + is_visited = np.zeros_like(transform_matrix, dtype=bool) + is_visited[src[0], src[1]] = True + traversal_queue = [src] + calculated = [(src[0] - 1) * n_cols + src[1]] + + def is_valid_neighbor(g_i, g_j): + return 0 <= g_i < n_rows and 0 <= g_j < n_cols \ + and not grid_map[g_i][g_j] + + while traversal_queue: + i, j = traversal_queue.pop(0) + for k, inc in enumerate(inc_order): + ni = i + inc[0] + nj = j + inc[1] + if is_valid_neighbor(ni, nj): + is_visited[i][j] = True + + # update transform_matrix + transform_matrix[i][j] = min( + transform_matrix[i][j], + transform_matrix[ni][nj] + cost[k] + alpha * eT[ni][nj]) + + if not is_visited[ni][nj] \ + and ((ni - 1) * n_cols + nj) not in calculated: + traversal_queue.append((ni, nj)) + calculated.append((ni - 1) * n_cols + nj) + + return transform_matrix + + +def get_search_order_increment(start, goal): + if start[0] >= goal[0] and start[1] >= goal[1]: + order = [[1, 0], [0, 1], [-1, 0], [0, -1], + [1, 1], [1, -1], [-1, 1], [-1, -1]] + elif start[0] <= goal[0] and start[1] >= goal[1]: + order = [[-1, 0], [0, 1], [1, 0], [0, -1], + [-1, 1], [-1, -1], [1, 1], [1, -1]] + elif start[0] >= goal[0] and start[1] <= goal[1]: + order = [[1, 0], [0, -1], [-1, 0], [0, 1], + [1, -1], [-1, -1], [1, 1], [-1, 1]] + elif start[0] <= goal[0] and start[1] <= goal[1]: + order = [[-1, 0], [0, -1], [0, 1], [1, 0], + [-1, -1], [-1, 1], [1, -1], [1, 1]] + else: + sys.exit('get_search_order_increment: cannot determine \ + start=>goal increment order') + return order + + +def wavefront(transform_matrix, start, goal): + """wavefront + + performing wavefront coverage path planning + + :param transform_matrix: the transform matrix + :param start: start point of planning + :param goal: goal point of planning + """ + + path = [] + n_rows, n_cols = transform_matrix.shape + + def is_valid_neighbor(g_i, g_j): + is_i_valid_bounded = 0 <= g_i < n_rows + is_j_valid_bounded = 0 <= g_j < n_cols + if is_i_valid_bounded and is_j_valid_bounded: + return not is_visited[g_i][g_j] and \ + transform_matrix[g_i][g_j] != float('inf') + return False + + inc_order = get_search_order_increment(start, goal) + + current_node = start + is_visited = np.zeros_like(transform_matrix, dtype=bool) + + while current_node != goal: + i, j = current_node + path.append((i, j)) + is_visited[i][j] = True + + max_T = float('-inf') + i_max = (-1, -1) + i_last = 0 + for i_last in range(len(path)): + current_node = path[-1 - i_last] # get latest node in path + for ci, cj in inc_order: + ni, nj = current_node[0] + ci, current_node[1] + cj + if is_valid_neighbor(ni, nj) and \ + transform_matrix[ni][nj] > max_T: + i_max = (ni, nj) + max_T = transform_matrix[ni][nj] + + if i_max != (-1, -1): + break + + if i_max == (-1, -1): + break + else: + current_node = i_max + if i_last != 0: + print('backtracing to', current_node) + path.append(goal) + + return path + + +def visualize_path(grid_map, start, goal, path): # pragma: no cover + oy, ox = start + gy, gx = goal + px, py = np.transpose(np.flipud(np.fliplr(path))) + + if not do_animation: + plt.imshow(grid_map, cmap='Greys') + plt.plot(ox, oy, "-xy") + plt.plot(px, py, "-r") + plt.plot(gx, gy, "-pg") + plt.show() + else: + for ipx, ipy in zip(px, py): + plt.cla() + # for stopping simulation with the esc key. + plt.gcf().canvas.mpl_connect( + 'key_release_event', + lambda event: [exit(0) if event.key == 'escape' else None]) + plt.imshow(grid_map, cmap='Greys') + plt.plot(ox, oy, "-xb") + plt.plot(px, py, "-r") + plt.plot(gx, gy, "-pg") + plt.plot(ipx, ipy, "or") + plt.axis("equal") + plt.grid(True) + plt.pause(0.1) + + +def main(): + dir_path = os.path.dirname(os.path.realpath(__file__)) + img = plt.imread(os.path.join(dir_path, 'map', 'test_2.png')) + img = 1 - img # revert pixel values + + start = (43, 0) + goal = (0, 0) + + # distance transform wavefront + DT = transform(img, goal, transform_type='distance') + DT_path = wavefront(DT, start, goal) + visualize_path(img, start, goal, DT_path) + + # path transform wavefront + PT = transform(img, goal, transform_type='path', alpha=0.01) + PT_path = wavefront(PT, start, goal) + visualize_path(img, start, goal, PT_path) + + +if __name__ == "__main__": + main() diff --git a/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefrontgpt2.py b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefrontgpt2.py new file mode 100644 index 0000000..ef76c9f --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/wavefrontgpt2.py @@ -0,0 +1,76 @@ +from queue import Queue + +def wavefront_planner(map_array, start_pos, goal_pos): + rows = len(map_array) + cols = len(map_array[0]) + + # Überprüfe, ob Start- und Zielposition innerhalb der Karte liegen + if (start_pos[0] < 0 or start_pos[0] >= rows or start_pos[1] < 0 or start_pos[1] >= cols or + goal_pos[0] < 0 or goal_pos[0] >= rows or goal_pos[1] < 0 or goal_pos[1] >= cols): + raise ValueError("Start or goal position is out of bounds.") + + # Erzeuge eine Kopie der Karte für den Wavefront-Algorithmus + wavefront_map = [[-1] * cols for _ in range(rows)] + + # Definiere die Bewegungsrichtungen (4-Wege-Bewegung: oben, unten, links, rechts) + directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] + + # Erzeuge eine Warteschlange für die Wellenfrontausbreitung + queue = Queue() + queue.put(goal_pos) + + # Führe die Wellenfrontausbreitung durch + wavefront_map[goal_pos[0]][goal_pos[1]] = 0 + while not queue.empty(): + current_pos = queue.get() + + # Überprüfe die Nachbarzellen + for direction in directions: + new_pos = (current_pos[0] + direction[0], current_pos[1] + direction[1]) + + # Überprüfe, ob die Nachbarzelle gültig und noch nicht besucht ist + if (0 <= new_pos[0] < rows and 0 <= new_pos[1] < cols and + wavefront_map[new_pos[0]][new_pos[1]] == -1 and map_array[new_pos[0]][new_pos[1]] == 0): + wavefront_map[new_pos[0]][new_pos[1]] = wavefront_map[current_pos[0]][current_pos[1]] + 1 + queue.put(new_pos) + + # Überprüfe, ob der Startpunkt erreichbar ist + if wavefront_map[start_pos[0]][start_pos[1]] == -1: + raise ValueError("Start position is unreachable.") + + # Verfolge den Pfad basierend auf der Wellenfront + path = [start_pos] + current_pos = start_pos + while current_pos != goal_pos: + next_pos = None + min_distance = float('inf') + + for direction in directions: + neighbor_pos = (current_pos[0] + direction[0], current_pos[1] + direction[1]) + + if (0 <= neighbor_pos[0] < rows and 0 <= neighbor_pos[1] < cols and + wavefront_map[neighbor_pos[0]][neighbor_pos[1]] < min_distance): + next_pos = neighbor_pos + min_distance = wavefront_map[neighbor_pos[0]][neighbor_pos[1]] + + if next_pos is None: + raise ValueError("No path found.") + + path.append(next_pos) + current_pos = next_pos + + return path + +# Beispielverwendung +map_array = [ + [0, 0, 0, 0], + [0, 1, 1, 0], + [0, 0, 0, 0], + [0, 1, 1, 0], +] + +start_pos = (0, 0) +goal_pos = (1, 3) + +path = wavefront_planner(map_array, start_pos, goal_pos) +print("Path:", path) diff --git a/ros2_ws/build/sphero_mini_controller/colcon_build.rc b/ros2_ws/build/sphero_mini_controller/colcon_build.rc new file mode 100644 index 0000000..573541a --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/colcon_build.rc @@ -0,0 +1 @@ +0 diff --git a/ros2_ws/build/sphero_mini_controller/colcon_command_prefix_setup_py.sh b/ros2_ws/build/sphero_mini_controller/colcon_command_prefix_setup_py.sh new file mode 100644 index 0000000..f9867d5 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/colcon_command_prefix_setup_py.sh @@ -0,0 +1 @@ +# generated from colcon_core/shell/template/command_prefix.sh.em diff --git a/ros2_ws/build/sphero_mini_controller/colcon_command_prefix_setup_py.sh.env b/ros2_ws/build/sphero_mini_controller/colcon_command_prefix_setup_py.sh.env new file mode 100644 index 0000000..0e1cc2a --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/colcon_command_prefix_setup_py.sh.env @@ -0,0 +1,61 @@ +AMENT_PREFIX_PATH=/opt/ros/humble +COLCON=1 +COLORTERM=truecolor +DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/999/bus +DESKTOP_SESSION=ubuntu +DISPLAY=:0 +GDMSESSION=ubuntu +GNOME_DESKTOP_SESSION_ID=this-is-deprecated +GNOME_SHELL_SESSION_MODE=ubuntu +GNOME_TERMINAL_SCREEN=/org/gnome/Terminal/screen/211b2ac6_0a43_44d5_b3bf_a0e22ff3787d +GNOME_TERMINAL_SERVICE=:1.128 +GPG_AGENT_INFO=/run/user/999/gnupg/S.gpg-agent:0:1 +GTK_MODULES=gail:atk-bridge +HOME=/home/ubuntu +IM_CONFIG_PHASE=1 +LANG=de_DE.UTF-8 +LANGUAGE=de_DE:en +LC_ADDRESS=de_DE.UTF-8 +LC_IDENTIFICATION=de_DE.UTF-8 +LC_MEASUREMENT=de_DE.UTF-8 +LC_MONETARY=de_DE.UTF-8 +LC_NAME=de_DE.UTF-8 +LC_NUMERIC=de_DE.UTF-8 +LC_PAPER=de_DE.UTF-8 +LC_TELEPHONE=de_DE.UTF-8 +LC_TIME=de_DE.UTF-8 +LD_LIBRARY_PATH=/opt/ros/humble/opt/rviz_ogre_vendor/lib:/opt/ros/humble/lib/x86_64-linux-gnu:/opt/ros/humble/lib +LOGNAME=ubuntu +OLDPWD=/home/ubuntu +PAPERSIZE=a4 +PATH=/opt/ros/humble/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin +PWD=/home/ubuntu/ros2_ws/build/sphero_mini_controller +PYTHONPATH=/opt/ros/humble/lib/python3.10/site-packages:/opt/ros/humble/local/lib/python3.10/dist-packages +QT_ACCESSIBILITY=1 +QT_IM_MODULE=ibus +ROS_DISTRO=humble +ROS_LOCALHOST_ONLY=0 +ROS_PYTHON_VERSION=3 +ROS_VERSION=2 +SESSION_MANAGER=local/ubuntu:@/tmp/.ICE-unix/2661,unix/ubuntu:/tmp/.ICE-unix/2661 +SHELL=/bin/bash +SHLVL=1 +SSH_AGENT_LAUNCHER=gnome-keyring +SSH_AUTH_SOCK=/run/user/999/keyring/ssh +SYSTEMD_EXEC_PID=2684 +TERM=xterm-256color +USER=ubuntu +USERNAME=ubuntu +VTE_VERSION=6800 +WINDOWPATH=2 +XAUTHORITY=/run/user/999/gdm/Xauthority +XDG_CONFIG_DIRS=/etc/xdg/xdg-ubuntu:/etc/xdg +XDG_CURRENT_DESKTOP=ubuntu:GNOME +XDG_DATA_DIRS=/usr/share/ubuntu:/usr/share/gnome:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop +XDG_MENU_PREFIX=gnome- +XDG_RUNTIME_DIR=/run/user/999 +XDG_SESSION_CLASS=user +XDG_SESSION_DESKTOP=ubuntu +XDG_SESSION_TYPE=x11 +XMODIFIERS=@im=ibus +_=/usr/bin/colcon diff --git a/ros2_ws/build/sphero_mini_controller/install.log b/ros2_ws/build/sphero_mini_controller/install.log new file mode 100644 index 0000000..0881f1a --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/install.log @@ -0,0 +1,22 @@ +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node.py +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__init__.py +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/WavefrontPlanner.py +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/treiber.py +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/_constants.py +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/blobErkennung.py +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node.cpython-310.pyc +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/__init__.cpython-310.pyc +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/WavefrontPlanner.cpython-310.pyc +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/treiber.cpython-310.pyc +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/_constants.cpython-310.pyc +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/blobErkennung.cpython-310.pyc +/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/ament_index/resource_index/packages/sphero_mini_controller +/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.xml +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/entry_points.txt +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/top_level.txt +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/zip-safe +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/PKG-INFO +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/SOURCES.txt +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/requires.txt +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/dependency_links.txt +/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller/sphero_mini diff --git a/ros2_ws/build/sphero_mini_controller/prefix_override/__pycache__/sitecustomize.cpython-310.pyc b/ros2_ws/build/sphero_mini_controller/prefix_override/__pycache__/sitecustomize.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2bd0054d0a2debc06129b0ef052f83ce7d0b4c8f GIT binary patch literal 301 zcmd1j<>g{vU|>+awmPMjfq~&Mh=Yuo85kHG7#J9e6&M&8QW#Pga~Pr+QW>%sQyJo! zQkYm6QW@ewY~~ckU<OU*mms}<RmS=m`MIh3rAeiEC8hdB`Nc-@<;D7$dBr7(IXU{p z1sSPD`SH1#d71Ia`FSNp`8heMMVc(Pn2RfmZ?P2=rKV+8+~O`uP0WdhaZ)Q%lcAiI z3`J}V3=ra15LTO#N;7j(aM+^{F)}{CEVZa8GbL5OII|=*xwN<>KR2@~Rj;7(7Kcr4 VeoARhsvRT9;~={@7&sUO7y-I#S<3(b literal 0 HcmV?d00001 diff --git a/ros2_ws/build/sphero_mini_controller/prefix_override/sitecustomize.py b/ros2_ws/build/sphero_mini_controller/prefix_override/sitecustomize.py new file mode 100644 index 0000000..231b97a --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/prefix_override/sitecustomize.py @@ -0,0 +1,3 @@ +import sys +sys.real_prefix = sys.prefix +sys.prefix = sys.exec_prefix = '/home/ubuntu/ros2_ws/install/sphero_mini_controller' diff --git a/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO new file mode 100644 index 0000000..1772b2b --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO @@ -0,0 +1,12 @@ +Metadata-Version: 2.1 +Name: sphero-mini-controller +Version: 0.0.0 +Summary: TODO: Package description +Home-page: UNKNOWN +Maintainer: ubuntu +Maintainer-email: ubuntu@todo.todo +License: TODO: License declaration +Platform: UNKNOWN + +UNKNOWN + diff --git a/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt new file mode 100644 index 0000000..450bb51 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt @@ -0,0 +1,20 @@ +package.xml +setup.cfg +setup.py +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/zip-safe +resource/sphero_mini_controller +sphero_mini_controller/WavefrontPlanner.py +sphero_mini_controller/__init__.py +sphero_mini_controller/_constants.py +sphero_mini_controller/blobErkennung.py +sphero_mini_controller/my_first_node.py +sphero_mini_controller/treiber.py +test/test_copyright.py +test/test_flake8.py +test/test_pep257.py \ No newline at end of file diff --git a/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt new file mode 100644 index 0000000..6a40ae6 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +sphero_mini = sphero_mini_controller.my_first_node:main + diff --git a/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt new file mode 100644 index 0000000..49fe098 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt @@ -0,0 +1 @@ +setuptools diff --git a/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt new file mode 100644 index 0000000..60845da --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt @@ -0,0 +1 @@ +sphero_mini_controller diff --git a/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/zip-safe b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ros2_ws/build/sphero_mini_controller/sphero_mini_controller.egg-info/zip-safe @@ -0,0 +1 @@ + diff --git a/ros2_ws/install/.colcon_install_layout b/ros2_ws/install/.colcon_install_layout new file mode 100644 index 0000000..3aad533 --- /dev/null +++ b/ros2_ws/install/.colcon_install_layout @@ -0,0 +1 @@ +isolated diff --git a/ros2_ws/install/COLCON_IGNORE b/ros2_ws/install/COLCON_IGNORE new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/install/_local_setup_util_ps1.py b/ros2_ws/install/_local_setup_util_ps1.py new file mode 100644 index 0000000..98348ee --- /dev/null +++ b/ros2_ws/install/_local_setup_util_ps1.py @@ -0,0 +1,404 @@ +# Copyright 2016-2019 Dirk Thomas +# Licensed under the Apache License, Version 2.0 + +import argparse +from collections import OrderedDict +import os +from pathlib import Path +import sys + + +FORMAT_STR_COMMENT_LINE = '# {comment}' +FORMAT_STR_SET_ENV_VAR = 'Set-Item -Path "Env:{name}" -Value "{value}"' +FORMAT_STR_USE_ENV_VAR = '$env:{name}' +FORMAT_STR_INVOKE_SCRIPT = '_colcon_prefix_powershell_source_script "{script_path}"' +FORMAT_STR_REMOVE_LEADING_SEPARATOR = '' +FORMAT_STR_REMOVE_TRAILING_SEPARATOR = '' + +DSV_TYPE_APPEND_NON_DUPLICATE = 'append-non-duplicate' +DSV_TYPE_PREPEND_NON_DUPLICATE = 'prepend-non-duplicate' +DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS = 'prepend-non-duplicate-if-exists' +DSV_TYPE_SET = 'set' +DSV_TYPE_SET_IF_UNSET = 'set-if-unset' +DSV_TYPE_SOURCE = 'source' + + +def main(argv=sys.argv[1:]): # noqa: D103 + parser = argparse.ArgumentParser( + description='Output shell commands for the packages in topological ' + 'order') + parser.add_argument( + 'primary_extension', + help='The file extension of the primary shell') + parser.add_argument( + 'additional_extension', nargs='?', + help='The additional file extension to be considered') + parser.add_argument( + '--merged-install', action='store_true', + help='All install prefixes are merged into a single location') + args = parser.parse_args(argv) + + packages = get_packages(Path(__file__).parent, args.merged_install) + + ordered_packages = order_packages(packages) + for pkg_name in ordered_packages: + if _include_comments(): + print( + FORMAT_STR_COMMENT_LINE.format_map( + {'comment': 'Package: ' + pkg_name})) + prefix = os.path.abspath(os.path.dirname(__file__)) + if not args.merged_install: + prefix = os.path.join(prefix, pkg_name) + for line in get_commands( + pkg_name, prefix, args.primary_extension, + args.additional_extension + ): + print(line) + + for line in _remove_ending_separators(): + print(line) + + +def get_packages(prefix_path, merged_install): + """ + Find packages based on colcon-specific files created during installation. + + :param Path prefix_path: The install prefix path of all packages + :param bool merged_install: The flag if the packages are all installed + directly in the prefix or if each package is installed in a subdirectory + named after the package + :returns: A mapping from the package name to the set of runtime + dependencies + :rtype: dict + """ + packages = {} + # since importing colcon_core isn't feasible here the following constant + # must match colcon_core.location.get_relative_package_index_path() + subdirectory = 'share/colcon-core/packages' + if merged_install: + # return if workspace is empty + if not (prefix_path / subdirectory).is_dir(): + return packages + # find all files in the subdirectory + for p in (prefix_path / subdirectory).iterdir(): + if not p.is_file(): + continue + if p.name.startswith('.'): + continue + add_package_runtime_dependencies(p, packages) + else: + # for each subdirectory look for the package specific file + for p in prefix_path.iterdir(): + if not p.is_dir(): + continue + if p.name.startswith('.'): + continue + p = p / subdirectory / p.name + if p.is_file(): + add_package_runtime_dependencies(p, packages) + + # remove unknown dependencies + pkg_names = set(packages.keys()) + for k in packages.keys(): + packages[k] = {d for d in packages[k] if d in pkg_names} + + return packages + + +def add_package_runtime_dependencies(path, packages): + """ + Check the path and if it exists extract the packages runtime dependencies. + + :param Path path: The resource file containing the runtime dependencies + :param dict packages: A mapping from package names to the sets of runtime + dependencies to add to + """ + content = path.read_text() + dependencies = set(content.split(os.pathsep) if content else []) + packages[path.name] = dependencies + + +def order_packages(packages): + """ + Order packages topologically. + + :param dict packages: A mapping from package name to the set of runtime + dependencies + :returns: The package names + :rtype: list + """ + # select packages with no dependencies in alphabetical order + to_be_ordered = list(packages.keys()) + ordered = [] + while to_be_ordered: + pkg_names_without_deps = [ + name for name in to_be_ordered if not packages[name]] + if not pkg_names_without_deps: + reduce_cycle_set(packages) + raise RuntimeError( + 'Circular dependency between: ' + ', '.join(sorted(packages))) + pkg_names_without_deps.sort() + pkg_name = pkg_names_without_deps[0] + to_be_ordered.remove(pkg_name) + ordered.append(pkg_name) + # remove item from dependency lists + for k in list(packages.keys()): + if pkg_name in packages[k]: + packages[k].remove(pkg_name) + return ordered + + +def reduce_cycle_set(packages): + """ + Reduce the set of packages to the ones part of the circular dependency. + + :param dict packages: A mapping from package name to the set of runtime + dependencies which is modified in place + """ + last_depended = None + while len(packages) > 0: + # get all remaining dependencies + depended = set() + for pkg_name, dependencies in packages.items(): + depended = depended.union(dependencies) + # remove all packages which are not dependent on + for name in list(packages.keys()): + if name not in depended: + del packages[name] + if last_depended: + # if remaining packages haven't changed return them + if last_depended == depended: + return packages.keys() + # otherwise reduce again + last_depended = depended + + +def _include_comments(): + # skipping comment lines when COLCON_TRACE is not set speeds up the + # processing especially on Windows + return bool(os.environ.get('COLCON_TRACE')) + + +def get_commands(pkg_name, prefix, primary_extension, additional_extension): + commands = [] + package_dsv_path = os.path.join(prefix, 'share', pkg_name, 'package.dsv') + if os.path.exists(package_dsv_path): + commands += process_dsv_file( + package_dsv_path, prefix, primary_extension, additional_extension) + return commands + + +def process_dsv_file( + dsv_path, prefix, primary_extension=None, additional_extension=None +): + commands = [] + if _include_comments(): + commands.append(FORMAT_STR_COMMENT_LINE.format_map({'comment': dsv_path})) + with open(dsv_path, 'r') as h: + content = h.read() + lines = content.splitlines() + + basenames = OrderedDict() + for i, line in enumerate(lines): + # skip over empty or whitespace-only lines + if not line.strip(): + continue + try: + type_, remainder = line.split(';', 1) + except ValueError: + raise RuntimeError( + "Line %d in '%s' doesn't contain a semicolon separating the " + 'type from the arguments' % (i + 1, dsv_path)) + if type_ != DSV_TYPE_SOURCE: + # handle non-source lines + try: + commands += handle_dsv_types_except_source( + type_, remainder, prefix) + except RuntimeError as e: + raise RuntimeError( + "Line %d in '%s' %s" % (i + 1, dsv_path, e)) from e + else: + # group remaining source lines by basename + path_without_ext, ext = os.path.splitext(remainder) + if path_without_ext not in basenames: + basenames[path_without_ext] = set() + assert ext.startswith('.') + ext = ext[1:] + if ext in (primary_extension, additional_extension): + basenames[path_without_ext].add(ext) + + # add the dsv extension to each basename if the file exists + for basename, extensions in basenames.items(): + if not os.path.isabs(basename): + basename = os.path.join(prefix, basename) + if os.path.exists(basename + '.dsv'): + extensions.add('dsv') + + for basename, extensions in basenames.items(): + if not os.path.isabs(basename): + basename = os.path.join(prefix, basename) + if 'dsv' in extensions: + # process dsv files recursively + commands += process_dsv_file( + basename + '.dsv', prefix, primary_extension=primary_extension, + additional_extension=additional_extension) + elif primary_extension in extensions and len(extensions) == 1: + # source primary-only files + commands += [ + FORMAT_STR_INVOKE_SCRIPT.format_map({ + 'prefix': prefix, + 'script_path': basename + '.' + primary_extension})] + elif additional_extension in extensions: + # source non-primary files + commands += [ + FORMAT_STR_INVOKE_SCRIPT.format_map({ + 'prefix': prefix, + 'script_path': basename + '.' + additional_extension})] + + return commands + + +def handle_dsv_types_except_source(type_, remainder, prefix): + commands = [] + if type_ in (DSV_TYPE_SET, DSV_TYPE_SET_IF_UNSET): + try: + env_name, value = remainder.split(';', 1) + except ValueError: + raise RuntimeError( + "doesn't contain a semicolon separating the environment name " + 'from the value') + try_prefixed_value = os.path.join(prefix, value) if value else prefix + if os.path.exists(try_prefixed_value): + value = try_prefixed_value + if type_ == DSV_TYPE_SET: + commands += _set(env_name, value) + elif type_ == DSV_TYPE_SET_IF_UNSET: + commands += _set_if_unset(env_name, value) + else: + assert False + elif type_ in ( + DSV_TYPE_APPEND_NON_DUPLICATE, + DSV_TYPE_PREPEND_NON_DUPLICATE, + DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS + ): + try: + env_name_and_values = remainder.split(';') + except ValueError: + raise RuntimeError( + "doesn't contain a semicolon separating the environment name " + 'from the values') + env_name = env_name_and_values[0] + values = env_name_and_values[1:] + for value in values: + if not value: + value = prefix + elif not os.path.isabs(value): + value = os.path.join(prefix, value) + if ( + type_ == DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS and + not os.path.exists(value) + ): + comment = f'skip extending {env_name} with not existing ' \ + f'path: {value}' + if _include_comments(): + commands.append( + FORMAT_STR_COMMENT_LINE.format_map({'comment': comment})) + elif type_ == DSV_TYPE_APPEND_NON_DUPLICATE: + commands += _append_unique_value(env_name, value) + else: + commands += _prepend_unique_value(env_name, value) + else: + raise RuntimeError( + 'contains an unknown environment hook type: ' + type_) + return commands + + +env_state = {} + + +def _append_unique_value(name, value): + global env_state + if name not in env_state: + if os.environ.get(name): + env_state[name] = set(os.environ[name].split(os.pathsep)) + else: + env_state[name] = set() + # append even if the variable has not been set yet, in case a shell script sets the + # same variable without the knowledge of this Python script. + # later _remove_ending_separators() will cleanup any unintentional leading separator + extend = FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + os.pathsep + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': extend + value}) + if value not in env_state[name]: + env_state[name].add(value) + else: + if not _include_comments(): + return [] + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +def _prepend_unique_value(name, value): + global env_state + if name not in env_state: + if os.environ.get(name): + env_state[name] = set(os.environ[name].split(os.pathsep)) + else: + env_state[name] = set() + # prepend even if the variable has not been set yet, in case a shell script sets the + # same variable without the knowledge of this Python script. + # later _remove_ending_separators() will cleanup any unintentional trailing separator + extend = os.pathsep + FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value + extend}) + if value not in env_state[name]: + env_state[name].add(value) + else: + if not _include_comments(): + return [] + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +# generate commands for removing prepended underscores +def _remove_ending_separators(): + # do nothing if the shell extension does not implement the logic + if FORMAT_STR_REMOVE_TRAILING_SEPARATOR is None: + return [] + + global env_state + commands = [] + for name in env_state: + # skip variables that already had values before this script started prepending + if name in os.environ: + continue + commands += [ + FORMAT_STR_REMOVE_LEADING_SEPARATOR.format_map({'name': name}), + FORMAT_STR_REMOVE_TRAILING_SEPARATOR.format_map({'name': name})] + return commands + + +def _set(name, value): + global env_state + env_state[name] = value + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value}) + return [line] + + +def _set_if_unset(name, value): + global env_state + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value}) + if env_state.get(name, os.environ.get(name)): + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +if __name__ == '__main__': # pragma: no cover + try: + rc = main() + except RuntimeError as e: + print(str(e), file=sys.stderr) + rc = 1 + sys.exit(rc) diff --git a/ros2_ws/install/_local_setup_util_sh.py b/ros2_ws/install/_local_setup_util_sh.py new file mode 100644 index 0000000..35c017b --- /dev/null +++ b/ros2_ws/install/_local_setup_util_sh.py @@ -0,0 +1,404 @@ +# Copyright 2016-2019 Dirk Thomas +# Licensed under the Apache License, Version 2.0 + +import argparse +from collections import OrderedDict +import os +from pathlib import Path +import sys + + +FORMAT_STR_COMMENT_LINE = '# {comment}' +FORMAT_STR_SET_ENV_VAR = 'export {name}="{value}"' +FORMAT_STR_USE_ENV_VAR = '${name}' +FORMAT_STR_INVOKE_SCRIPT = 'COLCON_CURRENT_PREFIX="{prefix}" _colcon_prefix_sh_source_script "{script_path}"' +FORMAT_STR_REMOVE_LEADING_SEPARATOR = 'if [ "$(echo -n ${name} | head -c 1)" = ":" ]; then export {name}=${{{name}#?}} ; fi' +FORMAT_STR_REMOVE_TRAILING_SEPARATOR = 'if [ "$(echo -n ${name} | tail -c 1)" = ":" ]; then export {name}=${{{name}%?}} ; fi' + +DSV_TYPE_APPEND_NON_DUPLICATE = 'append-non-duplicate' +DSV_TYPE_PREPEND_NON_DUPLICATE = 'prepend-non-duplicate' +DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS = 'prepend-non-duplicate-if-exists' +DSV_TYPE_SET = 'set' +DSV_TYPE_SET_IF_UNSET = 'set-if-unset' +DSV_TYPE_SOURCE = 'source' + + +def main(argv=sys.argv[1:]): # noqa: D103 + parser = argparse.ArgumentParser( + description='Output shell commands for the packages in topological ' + 'order') + parser.add_argument( + 'primary_extension', + help='The file extension of the primary shell') + parser.add_argument( + 'additional_extension', nargs='?', + help='The additional file extension to be considered') + parser.add_argument( + '--merged-install', action='store_true', + help='All install prefixes are merged into a single location') + args = parser.parse_args(argv) + + packages = get_packages(Path(__file__).parent, args.merged_install) + + ordered_packages = order_packages(packages) + for pkg_name in ordered_packages: + if _include_comments(): + print( + FORMAT_STR_COMMENT_LINE.format_map( + {'comment': 'Package: ' + pkg_name})) + prefix = os.path.abspath(os.path.dirname(__file__)) + if not args.merged_install: + prefix = os.path.join(prefix, pkg_name) + for line in get_commands( + pkg_name, prefix, args.primary_extension, + args.additional_extension + ): + print(line) + + for line in _remove_ending_separators(): + print(line) + + +def get_packages(prefix_path, merged_install): + """ + Find packages based on colcon-specific files created during installation. + + :param Path prefix_path: The install prefix path of all packages + :param bool merged_install: The flag if the packages are all installed + directly in the prefix or if each package is installed in a subdirectory + named after the package + :returns: A mapping from the package name to the set of runtime + dependencies + :rtype: dict + """ + packages = {} + # since importing colcon_core isn't feasible here the following constant + # must match colcon_core.location.get_relative_package_index_path() + subdirectory = 'share/colcon-core/packages' + if merged_install: + # return if workspace is empty + if not (prefix_path / subdirectory).is_dir(): + return packages + # find all files in the subdirectory + for p in (prefix_path / subdirectory).iterdir(): + if not p.is_file(): + continue + if p.name.startswith('.'): + continue + add_package_runtime_dependencies(p, packages) + else: + # for each subdirectory look for the package specific file + for p in prefix_path.iterdir(): + if not p.is_dir(): + continue + if p.name.startswith('.'): + continue + p = p / subdirectory / p.name + if p.is_file(): + add_package_runtime_dependencies(p, packages) + + # remove unknown dependencies + pkg_names = set(packages.keys()) + for k in packages.keys(): + packages[k] = {d for d in packages[k] if d in pkg_names} + + return packages + + +def add_package_runtime_dependencies(path, packages): + """ + Check the path and if it exists extract the packages runtime dependencies. + + :param Path path: The resource file containing the runtime dependencies + :param dict packages: A mapping from package names to the sets of runtime + dependencies to add to + """ + content = path.read_text() + dependencies = set(content.split(os.pathsep) if content else []) + packages[path.name] = dependencies + + +def order_packages(packages): + """ + Order packages topologically. + + :param dict packages: A mapping from package name to the set of runtime + dependencies + :returns: The package names + :rtype: list + """ + # select packages with no dependencies in alphabetical order + to_be_ordered = list(packages.keys()) + ordered = [] + while to_be_ordered: + pkg_names_without_deps = [ + name for name in to_be_ordered if not packages[name]] + if not pkg_names_without_deps: + reduce_cycle_set(packages) + raise RuntimeError( + 'Circular dependency between: ' + ', '.join(sorted(packages))) + pkg_names_without_deps.sort() + pkg_name = pkg_names_without_deps[0] + to_be_ordered.remove(pkg_name) + ordered.append(pkg_name) + # remove item from dependency lists + for k in list(packages.keys()): + if pkg_name in packages[k]: + packages[k].remove(pkg_name) + return ordered + + +def reduce_cycle_set(packages): + """ + Reduce the set of packages to the ones part of the circular dependency. + + :param dict packages: A mapping from package name to the set of runtime + dependencies which is modified in place + """ + last_depended = None + while len(packages) > 0: + # get all remaining dependencies + depended = set() + for pkg_name, dependencies in packages.items(): + depended = depended.union(dependencies) + # remove all packages which are not dependent on + for name in list(packages.keys()): + if name not in depended: + del packages[name] + if last_depended: + # if remaining packages haven't changed return them + if last_depended == depended: + return packages.keys() + # otherwise reduce again + last_depended = depended + + +def _include_comments(): + # skipping comment lines when COLCON_TRACE is not set speeds up the + # processing especially on Windows + return bool(os.environ.get('COLCON_TRACE')) + + +def get_commands(pkg_name, prefix, primary_extension, additional_extension): + commands = [] + package_dsv_path = os.path.join(prefix, 'share', pkg_name, 'package.dsv') + if os.path.exists(package_dsv_path): + commands += process_dsv_file( + package_dsv_path, prefix, primary_extension, additional_extension) + return commands + + +def process_dsv_file( + dsv_path, prefix, primary_extension=None, additional_extension=None +): + commands = [] + if _include_comments(): + commands.append(FORMAT_STR_COMMENT_LINE.format_map({'comment': dsv_path})) + with open(dsv_path, 'r') as h: + content = h.read() + lines = content.splitlines() + + basenames = OrderedDict() + for i, line in enumerate(lines): + # skip over empty or whitespace-only lines + if not line.strip(): + continue + try: + type_, remainder = line.split(';', 1) + except ValueError: + raise RuntimeError( + "Line %d in '%s' doesn't contain a semicolon separating the " + 'type from the arguments' % (i + 1, dsv_path)) + if type_ != DSV_TYPE_SOURCE: + # handle non-source lines + try: + commands += handle_dsv_types_except_source( + type_, remainder, prefix) + except RuntimeError as e: + raise RuntimeError( + "Line %d in '%s' %s" % (i + 1, dsv_path, e)) from e + else: + # group remaining source lines by basename + path_without_ext, ext = os.path.splitext(remainder) + if path_without_ext not in basenames: + basenames[path_without_ext] = set() + assert ext.startswith('.') + ext = ext[1:] + if ext in (primary_extension, additional_extension): + basenames[path_without_ext].add(ext) + + # add the dsv extension to each basename if the file exists + for basename, extensions in basenames.items(): + if not os.path.isabs(basename): + basename = os.path.join(prefix, basename) + if os.path.exists(basename + '.dsv'): + extensions.add('dsv') + + for basename, extensions in basenames.items(): + if not os.path.isabs(basename): + basename = os.path.join(prefix, basename) + if 'dsv' in extensions: + # process dsv files recursively + commands += process_dsv_file( + basename + '.dsv', prefix, primary_extension=primary_extension, + additional_extension=additional_extension) + elif primary_extension in extensions and len(extensions) == 1: + # source primary-only files + commands += [ + FORMAT_STR_INVOKE_SCRIPT.format_map({ + 'prefix': prefix, + 'script_path': basename + '.' + primary_extension})] + elif additional_extension in extensions: + # source non-primary files + commands += [ + FORMAT_STR_INVOKE_SCRIPT.format_map({ + 'prefix': prefix, + 'script_path': basename + '.' + additional_extension})] + + return commands + + +def handle_dsv_types_except_source(type_, remainder, prefix): + commands = [] + if type_ in (DSV_TYPE_SET, DSV_TYPE_SET_IF_UNSET): + try: + env_name, value = remainder.split(';', 1) + except ValueError: + raise RuntimeError( + "doesn't contain a semicolon separating the environment name " + 'from the value') + try_prefixed_value = os.path.join(prefix, value) if value else prefix + if os.path.exists(try_prefixed_value): + value = try_prefixed_value + if type_ == DSV_TYPE_SET: + commands += _set(env_name, value) + elif type_ == DSV_TYPE_SET_IF_UNSET: + commands += _set_if_unset(env_name, value) + else: + assert False + elif type_ in ( + DSV_TYPE_APPEND_NON_DUPLICATE, + DSV_TYPE_PREPEND_NON_DUPLICATE, + DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS + ): + try: + env_name_and_values = remainder.split(';') + except ValueError: + raise RuntimeError( + "doesn't contain a semicolon separating the environment name " + 'from the values') + env_name = env_name_and_values[0] + values = env_name_and_values[1:] + for value in values: + if not value: + value = prefix + elif not os.path.isabs(value): + value = os.path.join(prefix, value) + if ( + type_ == DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS and + not os.path.exists(value) + ): + comment = f'skip extending {env_name} with not existing ' \ + f'path: {value}' + if _include_comments(): + commands.append( + FORMAT_STR_COMMENT_LINE.format_map({'comment': comment})) + elif type_ == DSV_TYPE_APPEND_NON_DUPLICATE: + commands += _append_unique_value(env_name, value) + else: + commands += _prepend_unique_value(env_name, value) + else: + raise RuntimeError( + 'contains an unknown environment hook type: ' + type_) + return commands + + +env_state = {} + + +def _append_unique_value(name, value): + global env_state + if name not in env_state: + if os.environ.get(name): + env_state[name] = set(os.environ[name].split(os.pathsep)) + else: + env_state[name] = set() + # append even if the variable has not been set yet, in case a shell script sets the + # same variable without the knowledge of this Python script. + # later _remove_ending_separators() will cleanup any unintentional leading separator + extend = FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + os.pathsep + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': extend + value}) + if value not in env_state[name]: + env_state[name].add(value) + else: + if not _include_comments(): + return [] + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +def _prepend_unique_value(name, value): + global env_state + if name not in env_state: + if os.environ.get(name): + env_state[name] = set(os.environ[name].split(os.pathsep)) + else: + env_state[name] = set() + # prepend even if the variable has not been set yet, in case a shell script sets the + # same variable without the knowledge of this Python script. + # later _remove_ending_separators() will cleanup any unintentional trailing separator + extend = os.pathsep + FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value + extend}) + if value not in env_state[name]: + env_state[name].add(value) + else: + if not _include_comments(): + return [] + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +# generate commands for removing prepended underscores +def _remove_ending_separators(): + # do nothing if the shell extension does not implement the logic + if FORMAT_STR_REMOVE_TRAILING_SEPARATOR is None: + return [] + + global env_state + commands = [] + for name in env_state: + # skip variables that already had values before this script started prepending + if name in os.environ: + continue + commands += [ + FORMAT_STR_REMOVE_LEADING_SEPARATOR.format_map({'name': name}), + FORMAT_STR_REMOVE_TRAILING_SEPARATOR.format_map({'name': name})] + return commands + + +def _set(name, value): + global env_state + env_state[name] = value + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value}) + return [line] + + +def _set_if_unset(name, value): + global env_state + line = FORMAT_STR_SET_ENV_VAR.format_map( + {'name': name, 'value': value}) + if env_state.get(name, os.environ.get(name)): + line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) + return [line] + + +if __name__ == '__main__': # pragma: no cover + try: + rc = main() + except RuntimeError as e: + print(str(e), file=sys.stderr) + rc = 1 + sys.exit(rc) diff --git a/ros2_ws/install/local_setup.bash b/ros2_ws/install/local_setup.bash new file mode 100644 index 0000000..efd5f8c --- /dev/null +++ b/ros2_ws/install/local_setup.bash @@ -0,0 +1,107 @@ +# generated from colcon_bash/shell/template/prefix.bash.em + +# This script extends the environment with all packages contained in this +# prefix path. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + _colcon_prefix_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" > /dev/null && pwd)" +else + _colcon_prefix_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prefix_bash_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prefix_bash_prepend_unique_value_IFS="$IFS" + IFS=":" + # start with the new value + _all_values="$_value" + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS="$_colcon_prefix_bash_prepend_unique_value_IFS" + unset _colcon_prefix_bash_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# add this prefix to the COLCON_PREFIX_PATH +_colcon_prefix_bash_prepend_unique_value COLCON_PREFIX_PATH "$_colcon_prefix_bash_COLCON_CURRENT_PREFIX" +unset _colcon_prefix_bash_prepend_unique_value + +# check environment variable for custom Python executable +if [ -n "$COLCON_PYTHON_EXECUTABLE" ]; then + if [ ! -f "$COLCON_PYTHON_EXECUTABLE" ]; then + echo "error: COLCON_PYTHON_EXECUTABLE '$COLCON_PYTHON_EXECUTABLE' doesn't exist" + return 1 + fi + _colcon_python_executable="$COLCON_PYTHON_EXECUTABLE" +else + # try the Python executable known at configure time + _colcon_python_executable="/usr/bin/python3" + # if it doesn't exist try a fall back + if [ ! -f "$_colcon_python_executable" ]; then + if ! /usr/bin/env python3 --version > /dev/null 2> /dev/null; then + echo "error: unable to find python3 executable" + return 1 + fi + _colcon_python_executable=`/usr/bin/env python3 -c "import sys; print(sys.executable)"` + fi +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# get all commands in topological order +_colcon_ordered_commands="$($_colcon_python_executable "$_colcon_prefix_bash_COLCON_CURRENT_PREFIX/_local_setup_util_sh.py" sh bash)" +unset _colcon_python_executable +if [ -n "$COLCON_TRACE" ]; then + echo "Execute generated script:" + echo "<<<" + echo "${_colcon_ordered_commands}" + echo ">>>" +fi +eval "${_colcon_ordered_commands}" +unset _colcon_ordered_commands + +unset _colcon_prefix_sh_source_script + +unset _colcon_prefix_bash_COLCON_CURRENT_PREFIX diff --git a/ros2_ws/install/local_setup.ps1 b/ros2_ws/install/local_setup.ps1 new file mode 100644 index 0000000..6f68c8d --- /dev/null +++ b/ros2_ws/install/local_setup.ps1 @@ -0,0 +1,55 @@ +# generated from colcon_powershell/shell/template/prefix.ps1.em + +# This script extends the environment with all packages contained in this +# prefix path. + +# check environment variable for custom Python executable +if ($env:COLCON_PYTHON_EXECUTABLE) { + if (!(Test-Path "$env:COLCON_PYTHON_EXECUTABLE" -PathType Leaf)) { + echo "error: COLCON_PYTHON_EXECUTABLE '$env:COLCON_PYTHON_EXECUTABLE' doesn't exist" + exit 1 + } + $_colcon_python_executable="$env:COLCON_PYTHON_EXECUTABLE" +} else { + # use the Python executable known at configure time + $_colcon_python_executable="/usr/bin/python3" + # if it doesn't exist try a fall back + if (!(Test-Path "$_colcon_python_executable" -PathType Leaf)) { + if (!(Get-Command "python3" -ErrorAction SilentlyContinue)) { + echo "error: unable to find python3 executable" + exit 1 + } + $_colcon_python_executable="python3" + } +} + +# function to source another script with conditional trace output +# first argument: the path of the script +function _colcon_prefix_powershell_source_script { + param ( + $_colcon_prefix_powershell_source_script_param + ) + # source script with conditional trace output + if (Test-Path $_colcon_prefix_powershell_source_script_param) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_prefix_powershell_source_script_param'" + } + . "$_colcon_prefix_powershell_source_script_param" + } else { + Write-Error "not found: '$_colcon_prefix_powershell_source_script_param'" + } +} + +# get all commands in topological order +$_colcon_ordered_commands = & "$_colcon_python_executable" "$(Split-Path $PSCommandPath -Parent)/_local_setup_util_ps1.py" ps1 + +# execute all commands in topological order +if ($env:COLCON_TRACE) { + echo "Execute generated script:" + echo "<<<" + $_colcon_ordered_commands.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries) | Write-Output + echo ">>>" +} +if ($_colcon_ordered_commands) { + $_colcon_ordered_commands.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries) | Invoke-Expression +} diff --git a/ros2_ws/install/local_setup.sh b/ros2_ws/install/local_setup.sh new file mode 100644 index 0000000..ef8aee8 --- /dev/null +++ b/ros2_ws/install/local_setup.sh @@ -0,0 +1,137 @@ +# generated from colcon_core/shell/template/prefix.sh.em + +# This script extends the environment with all packages contained in this +# prefix path. + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_prefix_sh_COLCON_CURRENT_PREFIX="/home/ubuntu/ros2_ws/install" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_prefix_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_prefix_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_prefix_sh_COLCON_CURRENT_PREFIX + return 1 + fi +else + _colcon_prefix_sh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prefix_sh_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prefix_sh_prepend_unique_value_IFS="$IFS" + IFS=":" + # start with the new value + _all_values="$_value" + _contained_value="" + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + _contained_value=1 + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + if [ -z "$_contained_value" ]; then + if [ -n "$COLCON_TRACE" ]; then + if [ "$_all_values" = "$_value" ]; then + echo "export $_listname=$_value" + else + echo "export $_listname=$_value:\$$_listname" + fi + fi + fi + unset _contained_value + # restore the field separator + IFS="$_colcon_prefix_sh_prepend_unique_value_IFS" + unset _colcon_prefix_sh_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# add this prefix to the COLCON_PREFIX_PATH +_colcon_prefix_sh_prepend_unique_value COLCON_PREFIX_PATH "$_colcon_prefix_sh_COLCON_CURRENT_PREFIX" +unset _colcon_prefix_sh_prepend_unique_value + +# check environment variable for custom Python executable +if [ -n "$COLCON_PYTHON_EXECUTABLE" ]; then + if [ ! -f "$COLCON_PYTHON_EXECUTABLE" ]; then + echo "error: COLCON_PYTHON_EXECUTABLE '$COLCON_PYTHON_EXECUTABLE' doesn't exist" + return 1 + fi + _colcon_python_executable="$COLCON_PYTHON_EXECUTABLE" +else + # try the Python executable known at configure time + _colcon_python_executable="/usr/bin/python3" + # if it doesn't exist try a fall back + if [ ! -f "$_colcon_python_executable" ]; then + if ! /usr/bin/env python3 --version > /dev/null 2> /dev/null; then + echo "error: unable to find python3 executable" + return 1 + fi + _colcon_python_executable=`/usr/bin/env python3 -c "import sys; print(sys.executable)"` + fi +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# get all commands in topological order +_colcon_ordered_commands="$($_colcon_python_executable "$_colcon_prefix_sh_COLCON_CURRENT_PREFIX/_local_setup_util_sh.py" sh)" +unset _colcon_python_executable +if [ -n "$COLCON_TRACE" ]; then + echo "_colcon_prefix_sh_source_script() { + if [ -f \"\$1\" ]; then + if [ -n \"\$COLCON_TRACE\" ]; then + echo \"# . \\\"\$1\\\"\" + fi + . \"\$1\" + else + echo \"not found: \\\"\$1\\\"\" 1>&2 + fi + }" + echo "# Execute generated script:" + echo "# <<<" + echo "${_colcon_ordered_commands}" + echo "# >>>" + echo "unset _colcon_prefix_sh_source_script" +fi +eval "${_colcon_ordered_commands}" +unset _colcon_ordered_commands + +unset _colcon_prefix_sh_source_script + +unset _colcon_prefix_sh_COLCON_CURRENT_PREFIX diff --git a/ros2_ws/install/local_setup.zsh b/ros2_ws/install/local_setup.zsh new file mode 100644 index 0000000..f7a8d90 --- /dev/null +++ b/ros2_ws/install/local_setup.zsh @@ -0,0 +1,120 @@ +# generated from colcon_zsh/shell/template/prefix.zsh.em + +# This script extends the environment with all packages contained in this +# prefix path. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + _colcon_prefix_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd)" +else + _colcon_prefix_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +_colcon_prefix_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prefix_zsh_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prefix_zsh_prepend_unique_value_IFS="$IFS" + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set + _colcon_prefix_zsh_convert_to_array _values + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS="$_colcon_prefix_zsh_prepend_unique_value_IFS" + unset _colcon_prefix_zsh_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# add this prefix to the COLCON_PREFIX_PATH +_colcon_prefix_zsh_prepend_unique_value COLCON_PREFIX_PATH "$_colcon_prefix_zsh_COLCON_CURRENT_PREFIX" +unset _colcon_prefix_zsh_prepend_unique_value +unset _colcon_prefix_zsh_convert_to_array + +# check environment variable for custom Python executable +if [ -n "$COLCON_PYTHON_EXECUTABLE" ]; then + if [ ! -f "$COLCON_PYTHON_EXECUTABLE" ]; then + echo "error: COLCON_PYTHON_EXECUTABLE '$COLCON_PYTHON_EXECUTABLE' doesn't exist" + return 1 + fi + _colcon_python_executable="$COLCON_PYTHON_EXECUTABLE" +else + # try the Python executable known at configure time + _colcon_python_executable="/usr/bin/python3" + # if it doesn't exist try a fall back + if [ ! -f "$_colcon_python_executable" ]; then + if ! /usr/bin/env python3 --version > /dev/null 2> /dev/null; then + echo "error: unable to find python3 executable" + return 1 + fi + _colcon_python_executable=`/usr/bin/env python3 -c "import sys; print(sys.executable)"` + fi +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# get all commands in topological order +_colcon_ordered_commands="$($_colcon_python_executable "$_colcon_prefix_zsh_COLCON_CURRENT_PREFIX/_local_setup_util_sh.py" sh zsh)" +unset _colcon_python_executable +if [ -n "$COLCON_TRACE" ]; then + echo "Execute generated script:" + echo "<<<" + echo "${_colcon_ordered_commands}" + echo ">>>" +fi +eval "${_colcon_ordered_commands}" +unset _colcon_ordered_commands + +unset _colcon_prefix_sh_source_script + +unset _colcon_prefix_zsh_COLCON_CURRENT_PREFIX diff --git a/ros2_ws/install/setup.bash b/ros2_ws/install/setup.bash new file mode 100644 index 0000000..4c55244 --- /dev/null +++ b/ros2_ws/install/setup.bash @@ -0,0 +1,31 @@ +# generated from colcon_bash/shell/template/prefix_chain.bash.em + +# This script extends the environment with the environment of other prefix +# paths which were sourced when this file was generated as well as all packages +# contained in this prefix path. + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_chain_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source chained prefixes +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script +COLCON_CURRENT_PREFIX="/opt/ros/humble" +_colcon_prefix_chain_bash_source_script "$COLCON_CURRENT_PREFIX/local_setup.bash" + +# source this prefix +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script +COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" > /dev/null && pwd)" +_colcon_prefix_chain_bash_source_script "$COLCON_CURRENT_PREFIX/local_setup.bash" + +unset COLCON_CURRENT_PREFIX +unset _colcon_prefix_chain_bash_source_script diff --git a/ros2_ws/install/setup.ps1 b/ros2_ws/install/setup.ps1 new file mode 100644 index 0000000..558e9b9 --- /dev/null +++ b/ros2_ws/install/setup.ps1 @@ -0,0 +1,29 @@ +# generated from colcon_powershell/shell/template/prefix_chain.ps1.em + +# This script extends the environment with the environment of other prefix +# paths which were sourced when this file was generated as well as all packages +# contained in this prefix path. + +# function to source another script with conditional trace output +# first argument: the path of the script +function _colcon_prefix_chain_powershell_source_script { + param ( + $_colcon_prefix_chain_powershell_source_script_param + ) + # source script with conditional trace output + if (Test-Path $_colcon_prefix_chain_powershell_source_script_param) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_prefix_chain_powershell_source_script_param'" + } + . "$_colcon_prefix_chain_powershell_source_script_param" + } else { + Write-Error "not found: '$_colcon_prefix_chain_powershell_source_script_param'" + } +} + +# source chained prefixes +_colcon_prefix_chain_powershell_source_script "/opt/ros/humble\local_setup.ps1" + +# source this prefix +$env:COLCON_CURRENT_PREFIX=(Split-Path $PSCommandPath -Parent) +_colcon_prefix_chain_powershell_source_script "$env:COLCON_CURRENT_PREFIX\local_setup.ps1" diff --git a/ros2_ws/install/setup.sh b/ros2_ws/install/setup.sh new file mode 100644 index 0000000..5b2b7fe --- /dev/null +++ b/ros2_ws/install/setup.sh @@ -0,0 +1,45 @@ +# generated from colcon_core/shell/template/prefix_chain.sh.em + +# This script extends the environment with the environment of other prefix +# paths which were sourced when this file was generated as well as all packages +# contained in this prefix path. + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX=/home/ubuntu/ros2_ws/install +if [ ! -z "$COLCON_CURRENT_PREFIX" ]; then + _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +elif [ ! -d "$_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX + return 1 +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_chain_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source chained prefixes +# setting COLCON_CURRENT_PREFIX avoids relying on the build time prefix of the sourced script +COLCON_CURRENT_PREFIX="/opt/ros/humble" +_colcon_prefix_chain_sh_source_script "$COLCON_CURRENT_PREFIX/local_setup.sh" + + +# source this prefix +# setting COLCON_CURRENT_PREFIX avoids relying on the build time prefix of the sourced script +COLCON_CURRENT_PREFIX="$_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX" +_colcon_prefix_chain_sh_source_script "$COLCON_CURRENT_PREFIX/local_setup.sh" + +unset _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX +unset _colcon_prefix_chain_sh_source_script +unset COLCON_CURRENT_PREFIX diff --git a/ros2_ws/install/setup.zsh b/ros2_ws/install/setup.zsh new file mode 100644 index 0000000..990d171 --- /dev/null +++ b/ros2_ws/install/setup.zsh @@ -0,0 +1,31 @@ +# generated from colcon_zsh/shell/template/prefix_chain.zsh.em + +# This script extends the environment with the environment of other prefix +# paths which were sourced when this file was generated as well as all packages +# contained in this prefix path. + +# function to source another script with conditional trace output +# first argument: the path of the script +_colcon_prefix_chain_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$1" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source chained prefixes +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script +COLCON_CURRENT_PREFIX="/opt/ros/humble" +_colcon_prefix_chain_zsh_source_script "$COLCON_CURRENT_PREFIX/local_setup.zsh" + +# source this prefix +# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script +COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd)" +_colcon_prefix_chain_zsh_source_script "$COLCON_CURRENT_PREFIX/local_setup.zsh" + +unset COLCON_CURRENT_PREFIX +unset _colcon_prefix_chain_zsh_source_script diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/PKG-INFO b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/PKG-INFO new file mode 100644 index 0000000..1772b2b --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/PKG-INFO @@ -0,0 +1,12 @@ +Metadata-Version: 2.1 +Name: sphero-mini-controller +Version: 0.0.0 +Summary: TODO: Package description +Home-page: UNKNOWN +Maintainer: ubuntu +Maintainer-email: ubuntu@todo.todo +License: TODO: License declaration +Platform: UNKNOWN + +UNKNOWN + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/SOURCES.txt b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/SOURCES.txt new file mode 100644 index 0000000..450bb51 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/SOURCES.txt @@ -0,0 +1,20 @@ +package.xml +setup.cfg +setup.py +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt +../../build/sphero_mini_controller/sphero_mini_controller.egg-info/zip-safe +resource/sphero_mini_controller +sphero_mini_controller/WavefrontPlanner.py +sphero_mini_controller/__init__.py +sphero_mini_controller/_constants.py +sphero_mini_controller/blobErkennung.py +sphero_mini_controller/my_first_node.py +sphero_mini_controller/treiber.py +test/test_copyright.py +test/test_flake8.py +test/test_pep257.py \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/dependency_links.txt b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/entry_points.txt b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/entry_points.txt new file mode 100644 index 0000000..6a40ae6 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +sphero_mini = sphero_mini_controller.my_first_node:main + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/requires.txt b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/requires.txt new file mode 100644 index 0000000..49fe098 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/requires.txt @@ -0,0 +1 @@ +setuptools diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/top_level.txt b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/top_level.txt new file mode 100644 index 0000000..60845da --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/top_level.txt @@ -0,0 +1 @@ +sphero_mini_controller diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/zip-safe b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info/zip-safe @@ -0,0 +1 @@ + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/GameWavefront.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/GameWavefront.py new file mode 100644 index 0000000..aae934f --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/GameWavefront.py @@ -0,0 +1,375 @@ +# Wavefront Planning +# Sajad Saeedi, Andrew Davison 2017 +# Implementation is based on the following reference +# +# H. Choset, K. M. Lynch, S. Hutchinson, G. Kantor, W. Burgard, L. E. Kavraki and S. Thrun, +# Principles of Robot Motion: Theory, Algorithms, and Implementations, +# MIT Press, Boston, 2005. +# http://www.cs.cmu.edu/afs/cs/Web/People/motionplanning/ +# +# From Chapter 4.5 - Wave-Front Planner +# This planner determines a path via gradient descent on the grid starting from the start. +# Essentially, the planner determines the path one pixel at a time. +# The wave-front planner essentially forms a potential function on the grid which has one local minimum and thus is resolution complete. +# The planner also determines the shortest path, but at the cost of coming dangerously close to obstacles. +# The major drawback of this method is that the planner has to search the entire space for a path + +import pygame, os, math, time, random +import numpy as np +try: + import pygame + from pygame import surfarray + from pygame.locals import * +except ImportError: + raise ImportError('Error Importing Pygame/surfarray') + +pygame.init() + +SCALE = 1 + +# set the width and height of the screen +WIDTH = 1500/SCALE +HEIGHT = 1000/SCALE + +# The region we will fill with obstacles +PLAYFIELDCORNERS = (-3.0, -3.0, 3.0, 3.0) + +# Barrier locations +barriers = [] +# barrier contents are (bx, by, visibilitymask) +for i in range(100): + (bx, by) = (random.uniform(PLAYFIELDCORNERS[0], PLAYFIELDCORNERS[2]), random.uniform(PLAYFIELDCORNERS[1], PLAYFIELDCORNERS[3])) + barrier = [bx, by, 0] + barriers.append(barrier) + +BARRIERRADIUS = 0.1 + + +ROBOTRADIUS = 0.15 +W = 2 * ROBOTRADIUS +SAFEDIST = 0.2 +BARRIERINFILATE = ROBOTRADIUS + +MAXVELOCITY = 0.5 #ms^(-1) max speed of each wheel +MAXACCELERATION = 0.5 #ms^(-2) max rate we can change speed of each wheel + +target = (PLAYFIELDCORNERS[2] + 1.0, 0) + +k = 160/SCALE # pixels per metre for graphics +u0 = WIDTH / 2 +v0 = HEIGHT / 2 + +x = PLAYFIELDCORNERS[0] - 0.5 +y = 0.0 +theta = 0.0 + +vL = 0.00 +vR = 0.00 + +size = [int(WIDTH), int(HEIGHT)] +screen = pygame.display.set_mode(size) +black = (20,20,40) + +# This makes the normal mouse pointer invisible in graphics window +pygame.mouse.set_visible(0) + +# time delta +dt = 0.1 + + +def surfdemo_show(array_img, name): + "displays a surface, waits for user to continue" + screen = pygame.display.set_mode(array_img.shape[:2], 0, 32) + surfarray.blit_array(screen, array_img) + pygame.display.flip() + pygame.display.set_caption(name) + while 1: + e = pygame.event.wait() + if e.type == MOUSEBUTTONDOWN: break + if e.type == KEYDOWN: break + + +def predictPosition(vL, vR, x, y, theta, dt): + + # Position update in time dt + + # Special cases + # Straight line motion + if (vL == vR): + xnew = x + vL * dt * math.cos(theta) + ynew = y + vL * dt * math.sin(theta) + thetanew = theta + # Pure rotation motion + elif (vL == -vR): + xnew = x + ynew = y + thetanew = theta + ((vR - vL) * dt / W) + else: + # Rotation and arc angle of general circular motion + R = W / 2.0 * (vR + vL) / (vR - vL) + deltatheta = (vR - vL) * dt / W + xnew = x + R * (math.sin(deltatheta + theta) - math.sin(theta)) + ynew = y - R * (math.cos(deltatheta + theta) - math.cos(theta)) + thetanew = theta + deltatheta + + return (xnew, ynew, thetanew) + + +def drawBarriers(barriers): + for barrier in barriers: + if(barrier[2] == 0): + pygame.draw.circle(screen, (0,20,80), (int(u0 + k * barrier[0]), int(v0 - k * barrier[1])), int(k * BARRIERRADIUS), 0) + else: + pygame.draw.circle(screen, (0,120,255), (int(u0 + k * barrier[0]), int(v0 - k * barrier[1])), int(k * BARRIERRADIUS), 0) + return + +def dialtebarrieres(imgin, barriers): + imgout = imgin + for barrier in barriers: + if(barrier[2] == 0): + center_u = int(u0 + k * barrier[0]) + center_v = int(v0 - k * barrier[1]) + RAD = BARRIERRADIUS+BARRIERINFILATE + radius = int(k * (RAD)) + radius2 = int(k * (BARRIERRADIUS)) + points = [(x,y) for x in range(center_u-radius, center_u+radius) for y in range(center_v-radius, center_v+radius)] + for pt in points: + vu = center_u - pt[0] + vv = center_v - pt[1] + distance = math.sqrt(vv*vv + vu*vu) + if (distance < radius and distance > radius2 and pt[0] < WIDTH-1 and pt[1] < HEIGHT-1): + imgout[pt[0],pt[1],0] = 0 + imgout[pt[0],pt[1],1] = 40 + imgout[pt[0],pt[1],2] = 80 + return imgout + +def MakeWaveFront(imgarray, start_uv, target_uv): + print ("building wavefront, please wait ... ") + heap = [] + newheap = [] + + u = target_uv[0] + v = target_uv[1] + + imgarray[:,:,0] = 0 # use channel 0 for planning + imgarray[u, v, 0] = 2 + + lastwave = 3 # start by setting nodes around target to 3 + moves = [(u + 1, v), (u - 1, v), (u, v - 1), (u, v + 1)] + for move in moves: + imgarray[move[0], move[1], 0] = 3 + heap.append(move) + + path = False + max_search = int(np.sqrt(WIDTH*WIDTH + HEIGHT*HEIGHT)) + for currentwave in range(4, max_search): + lastwave = lastwave + 1 + while(heap != []): + position = heap.pop() + (u, v) = position + moves = [(u + 1, v), (u - 1, v), (u, v + 1), (u, v - 1)] + for move in moves: + if(imgarray[move[0], move[1], 2] != 80): + if(imgarray[move[0], move[1], 0] == 0 and imgarray[position[0], position[1], 0] == currentwave - 1 and move[0] < WIDTH-1 and move[1] < HEIGHT-1 and move[0]>2 and move[1]>2): + imgarray[move[0], move[1], 0] = currentwave + newheap.append(move) + if(move == start_uv): + path = True + break + if(path == True): + break + if(path == True): + break + heap = newheap + newheap = [] + return imgarray, currentwave + + +def FindPath(imgarray, start_uv, currentwave): + trajectory = [] + nextpt = start_uv + path = [] + for backwave in range(currentwave-1,2,-1): + path.append(nextpt) + (u,v) = nextpt + values = [] + val = [] + moves = [(u + 1, v), (u - 1, v), (u, v + 1), (u, v - 1), (u + 1, v + 1), (u - 1, v - 1), (u + 1, v - 1), (u - 1, v - 1)] + for move in moves: + val.append(imgarray[move[0], move[1], 0]) + if(imgarray[move[0], move[1], 0] == backwave): + values.append(imgarray[move[0], move[1], 0]) + minimum = min(values) + indices = [i for i, v in enumerate(val) if v == minimum] + nextid = random.choice(indices) + nextpt = moves[nextid] + return path + +def GetControl(x,y,theta, waypoint): + wpu = waypoint[0] + wpv = waypoint[1] + + vt = 0.2 + u = u0 + k * x + v = v0 - k * y + vector = (wpu - u, -wpv + v) + vectorangle = math.atan2(vector[1], vector[0]) + psi = vectorangle - theta + + if(abs(psi) > (2*3.14/180)): + if(psi > 0): + vR = vt/2 + vL = -vt/2 + else: + vR = -vt/2 + vL = vt/2 + else: + wlx = x - (W/2.0) * math.sin(theta) + wly = y + (W/2.0) * math.cos(theta) + ulx = u0 + k * wlx + vlx = v0 - k * wly + dl = math.sqrt((ulx-wpu)*(ulx-wpu) + (vlx-wpv)*(vlx-wpv)) + + wrx = x + (W/2.0) * math.sin(theta) + wry = y - (W/2.0) * math.cos(theta) + urx = u0 + k * wrx + vrx = v0 - k * wry + dr = math.sqrt((urx-wpu)*(urx-wpu) + (vrx-wpv)*(vrx-wpv)) + + vR = 1*(2*vt)/(1+(dl/dr)) + vL = 1*(2*vt - vR) + + return vL, vR + +def AnimateRobot(x,y,theta): + # Draw robot + u = u0 + k * x + v = v0 - k * y + pygame.draw.circle(screen, (255,255,255), (int(u), int(v)), int(k * ROBOTRADIUS), 3) + # Draw wheels + # left wheel centre + wlx = x - (W/2.0) * math.sin(theta) + wly = y + (W/2.0) * math.cos(theta) + ulx = u0 + k * wlx + vlx = v0 - k * wly + WHEELBLOB = 0.04 + pygame.draw.circle(screen, (0,0,255), (int(ulx), int(vlx)), int(k * WHEELBLOB)) + # right wheel centre + wrx = x + (W/2.0) * math.sin(theta) + wry = y - (W/2.0) * math.cos(theta) + urx = u0 + k * wrx + vrx = v0 - k * wry + pygame.draw.circle(screen, (0,0,255), (int(urx), int(vrx)), int(k * WHEELBLOB)) + + time.sleep(dt / 5) + pygame.display.flip() + #time.sleep(0.2) + +def AnimateNavigation(barriers, waypint, path): + screen.fill(black) + drawBarriers(barriers) + + for pt in path: + screen.set_at(pt, (255,255,255)) + + pygame.draw.circle(screen, (255,100,0), target_uv, int(k * ROBOTRADIUS), 0) + pygame.draw.circle(screen, (255,100,0), (int(u0 + k * target[0]), int(v0 - k * target[1])), int(k * ROBOTRADIUS), 0) + pygame.draw.circle(screen, (255,0,255), (int(waypint[0]), int(waypint[1])), int(5)) + +def AnimatePath(imgarray, path, start_uv): + for pt in path: + imgarray[pt[0], pt[1], 0] = 0 + imgarray[pt[0], pt[1], 1] = 255 + imgarray[pt[0], pt[1], 2] = 255 + + #imgarray[:,:,0] /= imgarray[:,:,0].max()/255.0 + scalefactor = imgarray[:,:,0].max()/255.0 + imgarray[:,:,0] = imgarray[:,:,0]/scalefactor + imgarray[:,:,0].astype(int) + imgarray[start_uv[0], start_uv[1], 0] = 0 + imgarray[start_uv[0], start_uv[1], 1] = 255 + imgarray[start_uv[0], start_uv[1], 2] = 255 + + surfdemo_show(imgarray, 'Wavefront Path Planning') + + +def GetWaypoint(x,y,theta, path, waypointIndex, waypointSeperation, target): + reset = False + waypoint = path[waypointIndex] + u = u0 + k * x + v = v0 - k * y + distance_to_wp = math.sqrt((waypoint[0]-u)**2+(waypoint[1]-v)**2) # todo compare distance in metrics + if(distance_to_wp < 3): + waypointIndex = waypointSeperation + waypointIndex + if(waypointIndex > len(path)): + waypointIndex = len(path) - 1 + + return waypoint, reset, waypointIndex + +def IsAtTarget(x,y,target): + disttotarget = math.sqrt((x - target[0])**2 + (y - target[1])**2) + if (disttotarget < 0.04): + return True + else: + return False + + +while(1): + start_uv = (int(u0 + k * x), int(v0 - k * y)) + target_uv = (int(u0 + k * target[0]), int(v0 - k * target[1])) + print ("start is: ", start_uv) + print ("goal is: ", target_uv) + + # prepare map of the world for plannign + screen.fill(black) + drawBarriers(barriers) + pygame.draw.circle(screen, (255,100,0), target_uv, int(k * ROBOTRADIUS), 0) + pygame.draw.circle(screen, (255,255,0), start_uv, int(k * ROBOTRADIUS), 0) + imgscreen8 = pygame.surfarray.array3d(screen) + imgscreen16 = np.array(imgscreen8, dtype=np.uint16) + drawBarriers(barriers) + imgarray = dialtebarrieres(imgscreen16, barriers) + pygame.display.flip() + pygame.display.set_caption('Wavefront Path Planning') + + # build wavefront, given the map, start point, and target point + imgarray, currentwave = MakeWaveFront(imgarray, start_uv, target_uv) + print ("press a key to start navaigation ... ") + + # find the path using the wavefront + path = FindPath(imgarray, start_uv, currentwave) + + # normlaize and show the path on the map + AnimatePath(imgarray, path, start_uv) + + # set start point + x = PLAYFIELDCORNERS[0] - 0.5 + y = 0 + theta = 0 + waypointSeperation = 40; + waypointIndex = waypointSeperation; + + # loop to navigate the path from start to target + while(1): + # get a waypoint to follow the path + (waypoint, reset, waypointIndex)= GetWaypoint(x,y,theta, path, waypointIndex, waypointSeperation, target) + + # reset the simulation, if robot is at target + if (IsAtTarget(x,y,target)): + target = (target[0], random.uniform(PLAYFIELDCORNERS[1], PLAYFIELDCORNERS[3])) + x = PLAYFIELDCORNERS[0] - 0.5 + y = 0.0 + theta = 0.0 + break + + + # calculate control signals to get to the next waypoint + (vL, vR) = GetControl(x,y,theta, waypoint) + + # Actually now move and update position based on chosen vL and vR + (x, y, theta) = predictPosition(vL, vR, x, y, theta, dt) + + # animate + AnimateNavigation(barriers, waypoint, path) + AnimateRobot(x,y,theta) + \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/WavefrontChatGpt.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/WavefrontChatGpt.py new file mode 100644 index 0000000..a358dfa --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/WavefrontChatGpt.py @@ -0,0 +1,53 @@ +import numpy as np +import cv2 +import matplotlib.pyplot as plt +import time + +class WaveFrontPlanner: + __wall = 999 + __goal = 1 + __nothing = '000' + __path = 'PATH' + + def __init__(self, mask_list): + if not isinstance(mask_list, np.ndarray): + raise ValueError("Invalid input type. 'mask_list' must be a numpy array.") + self.__mapOfWorld = np.array([['999' if j > 200 else '000' for j in row] for row in mask_list]) + + def minSurroundingNodeValue(self, x, y): + values = [self.__mapOfWorld[x + 1][y], self.__mapOfWorld[x - 1][y], + self.__mapOfWorld[x][y + 1], self.__mapOfWorld[x][y - 1]] + min_value = min(value for value in values if value != self.__nothing) + min_index = values.index(min_value) + 1 + return min_value, min_index + + def waveFrontAlgorithm(self): + goal_coordinates = np.where(self.__mapOfWorld == self.__goal) + goal_x, goal_y = goal_coordinates[0][0], goal_coordinates[1][0] + queue = [(goal_x, goal_y)] + + while queue: + x, y = queue.pop(0) + + if self.__mapOfWorld[x][y] == self.__nothing: + min_value, min_index = self.minSurroundingNodeValue(x, y) + self.__mapOfWorld[x][y] = min_value + 1 + queue.append((x + 1, y)) # Move down + queue.append((x - 1, y)) # Move up + queue.append((x, y + 1)) # Move right + queue.append((x, y - 1)) # Move left + + self.__mapOfWorld[goal_x][goal_y] = self.__goal + + def visualizeMap(self): + plt.imshow(cv2.flip(self.__mapOfWorld, 0), cmap='hot', interpolation='nearest') + plt.colorbar() + plt.show() + +# Beispielverwendung: +mask_list = np.zeros((200, 200)) +mask_list[50:150, 50:150] = 1 + +planner = WaveFrontPlanner(mask_list) +planner.waveFrontAlgorithm() +planner.visualizeMap() diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/WavefrontPlanner.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/WavefrontPlanner.py new file mode 100644 index 0000000..13cebc9 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/WavefrontPlanner.py @@ -0,0 +1,397 @@ +############################################################################ +# WAVEFRONT ALGORITHM +# Adapted to Python Code By Darin Velarde +# Fri Jan 29 13:56:53 PST 2010 +# from C code from John Palmisano +# (www.societyofrobots.com) +############################################################################ +import cv2 +import numpy as np +import matplotlib.pyplot as plt +import time + +class waveFrontPlanner: + ############################################################################ + # WAVEFRONT ALGORITHM + # Adapted to Python Code By Darin Velarde + # Fri Jan 29 13:56:53 PST 2010 + # from C code from John Palmisano + # (www.societyofrobots.com) + ############################################################################ + + def __init__(self, mapOfWorld, slow=False): + self.__slow = slow + self.__mapOfWorld = mapOfWorld + if str(type(mapOfWorld)).find("numpy") != -1: + #If its a numpy array + self.__height, self.__width = self.__mapOfWorld.shape + else: + self.__height, self.__width = len(self.__mapOfWorld), len(self.__mapOfWorld[0]) + + self.__nothing = 000 + self.__wall = 999 + self.__goal = 1 + self.__path = "PATH" + + self.__finalPath = [] + + #Robot value + self.__robot = 254 + #Robot default Location + self.__robot_x = 0 + self.__robot_y = 0 + + #default goal location + self.__goal_x = 8 + self.__goal_y = 9 + + #temp variables + self.__temp_A = 0 + self.__temp_B = 0 + self.__counter = 0 + self.__steps = 0 #determine how processor intensive the algorithm was + + #when searching for a node with a lower value + self.__minimum_node = 250 + self.__min_node_location = 250 + self.__new_state = 1 + self.__old_state = 1 + self.__reset_min = 250 #above this number is a special (wall or robot) + ########################################################################### + + def setRobotPosition(self, x, y): + """ + Sets the robot's current position + + """ + + self.__robot_x = x + self.__robot_y = y + ########################################################################### + + def setGoalPosition(self, x, y): + """ + Sets the goal position. + + """ + + self.__goal_x = x + self.__goal_y = y + ########################################################################### + + def robotPosition(self): + return (self.__robot_x, self.__robot_y) + ########################################################################### + + def goalPosition(self): + return (self.__goal_x, self.__goal_y) + ########################################################################### + + def run(self, prnt=False): + """ + The entry point for the robot algorithm to use wavefront propagation. + + """ + + path = [] + while self.__mapOfWorld[self.__robot_x][self.__robot_y] != self.__goal: + if self.__steps > 10000: + print ("Cannot find a path.") + return + #find new location to go to + self.__new_state = self.propagateWavefront() + #update location of robot + if self.__new_state == 1: + self.__robot_x -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" % (self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 2: + self.__robot_y += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 3: + self.__robot_x += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 4: + self.__robot_y -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + self.__old_state = self.__new_state + msg = "Found the goal in %i steps:\n" % self.__steps + msg += "mapOfWorld size= %i %i\n\n" % (self.__height, self.__width) + if prnt: + print(msg) + self.printMap() + return path + ########################################################################### + + def propagateWavefront(self, prnt=False): + self.unpropagate() + #Old robot location was deleted, store new robot location in mapOfWorld + self.__mapOfWorld[self.__robot_x][self.__robot_y] = self.__robot + self.__path = self.__robot + #start location to begin scan at goal location + self.__mapOfWorld[self.__goal_x][self.__goal_y] = self.__goal + counter = 0 + while counter < 200: #allows for recycling until robot is found + x = 0 + y = 0 + #time.sleep(0.00001) + #while the mapOfWorld hasnt been fully scanned + while x < self.__height and y < self.__width: + #if this location is a wall or the goal, just ignore it + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal: + #a full trail to the robot has been located, finished! + minLoc = self.minSurroundingNodeValue(x, y) + if minLoc < self.__reset_min and \ + self.__mapOfWorld[x][y] == self.__robot: + if prnt: + print("Finished Wavefront:\n") + self.printMap() + # Tell the robot to move after this return. + return self.__min_node_location + #record a value in to this node + elif self.__minimum_node != self.__reset_min: + #if this isnt here, 'nothing' will go in the location + self.__mapOfWorld[x][y] = self.__minimum_node + 1 + #go to next node and/or row + y += 1 + if y == self.__width and x != self.__height: + x += 1 + y = 0 + #print self.__robot_x, self.__robot_y + if prnt: + print("Sweep #: %i\n" % (counter + 1)) + self.printMap() + self.__steps += 1 + counter += 1 + return 0 + ########################################################################### + + def unpropagate(self): + """ + clears old path to determine new path + stay within boundary + + """ + + for x in range(0, self.__height): + for y in range(0, self.__width): + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal and \ + self.__mapOfWorld[x][y] != self.__path: + #if this location is a wall or goal, just ignore it + self.__mapOfWorld[x][y] = self.__nothing #clear that space + ########################################################################### + + def minSurroundingNodeValue(self, x, y): + """ + this method looks at a node and returns the lowest value around that + node. + + """ + + #reset minimum + self.__minimum_node = self.__reset_min + #down + if x < self.__height -1: + if self.__mapOfWorld[x + 1][y] < self.__minimum_node and \ + self.__mapOfWorld[x + 1][y] != self.__nothing: + #find the lowest number node, and exclude empty nodes (0's) + self.__minimum_node = self.__mapOfWorld[x + 1][y] + self.__min_node_location = 3 + #up + if x > 0: + if self.__mapOfWorld[x-1][y] < self.__minimum_node and \ + self.__mapOfWorld[x-1][y] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x-1][y] + self.__min_node_location = 1 + #right + if y < self.__width -1: + if self.__mapOfWorld[x][y + 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y + 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y + 1] + self.__min_node_location = 2 + #left + if y > 0: + if self.__mapOfWorld[x][y - 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y - 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y-1] + self.__min_node_location = 4 + return self.__minimum_node + ########################################################################### + + def printMap(self): + """ + Prints out the map of this instance of the class. + + """ + + msg = '' + for temp_B in range(0, self.__height): + for temp_A in range(0, self.__width): + if self.__mapOfWorld[temp_B][temp_A] == self.__wall: + msg += "%04s" % "[#]" + elif self.__mapOfWorld[temp_B][temp_A] == self.__robot: + msg += "%04s" % "-" + elif self.__mapOfWorld[temp_B][temp_A] == self.__goal: + msg += "%04s" % "G" + else: + msg += "%04s" % str(self.__mapOfWorld[temp_B][temp_A]) + msg += "\n\n" + msg += "\n\n" + print(msg) + # + if self.__slow == True: + time.sleep(0.05) +############################################################################ + +class mapCreate: + ############################################################################ + + def create_map(self, scale,img): + + # Map erstellen + + # Img Objekt + #cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + #success, img = cap.read() + + #Karte von Bild + #succ, img = cv2.imread(2) + print('Original Dimensions : ',img.shape) + + scale_percent = 100-scale # percent of original size + width = int(img.shape[1] * scale_percent / 100) + height = int(img.shape[0] * scale_percent / 100) + dim = (width, height) + + # resize image + resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA) + print('Resized Dimensions : ',resized.shape) + + gray = cv2.cvtColor(resized, cv2.COLOR_RGB2GRAY) + mask = cv2.inRange(gray, np.array([90]), np.array([255])) + + mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + mask_list= np.ndarray.tolist(mask) + + #Markiere alle leeren Zellen mit 000 und alle Zellen mit Hinderniss mit 999 + for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['999' if j > 200 else '000' for j in mask_list[i]] + + mapOfWorld = np.array(mapOfWorld, dtype = int) + mapOfWorldSized = mapOfWorld.copy() + + e = 3 + #Hindernisse Größer machen + for i in range(0,mapOfWorld.shape[0]): + for j in range(0,mapOfWorld.shape[1]): + for r in range(0,e): + try: + if (mapOfWorldSized[i][j] == 999): + mapOfWorld[i][j+e] = 999 + mapOfWorld[i+e][j] = 999 + mapOfWorld[i-e][j] = 999 + mapOfWorld[i][j-e] = 999 + mapOfWorld[i+e][j+e] = 999 + mapOfWorld[i+e][j-e] = 999 + mapOfWorld[i-e][j+e] = 999 + mapOfWorld[i-e][j-e] = 999 + except Exception: + continue + + return mapOfWorld + ############################################################################ + + def scale_koord(self, sx, sy, gx, gy,scale): + scale_percent = 100-scale # percent of original size + + sx = int(sx*scale_percent/100) + sy = int(sy*scale_percent/100) + gx = int(gx*scale_percent/100) + gy = int(gy*scale_percent/100) + + return sx,sy,gx,gy + ############################################################################ + + def rescale_koord(self, path,scale): + scale_percent = 100-scale # percent of original size100 + + path = np.array(path) + + for i in range(len(path)): + path[i] = path[i]*100/scale_percent + + return path + ############################################################################ + + def calc_trans(self, x, y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans +############################################################################### + +if __name__ == "__main__": + """ + X is vertical, Y is horizontal + + """ + + Map = mapCreate() #Map Objekt erzeugen + + #Verkleinerungsfaktor in % + scale = 85 + + img = cv2.imread("D:/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/karte.jpg", cv2.COLOR_RGB2GRAY) + #cap = cv2.VideoCapture(2) + mapWorld = Map.create_map(scale, img) + height, width = img.shape[:2] + + #Angaben in Weltkoordinaten + sy = 300 #X-Koordinate im Weltkoordinaten System + sx = 200 #Y-Koordinate im Weltkoordinaten System + + gy = 700 #X-Koordinate im Weltkordinaten System + gx = 240 #Y-Koordinate im Weltkoordinaten System + + sx,sy,gx,gy = Map.scale_koord(sx,sy,gx,gy,scale) #Kordinaten Tauschen X,Y + + start = time.time() + planner = waveFrontPlanner(mapWorld) + planner.setGoalPosition(gx,gy) + planner.setRobotPosition(sx,sy) + path = planner.run(False) + end = time.time() + print("Took %f seconds to run wavefront simulation" % (end - start)) + + path = Map.rescale_koord(path, scale) + #print(path) +#%% Plot + #Programm Koordinaten + imgTrans = cv2.transpose(img) # X und Y-Achse im Bild tauschen + imgPlot, ax1 = plt.subplots() + + ax1.set_title('Programm Koordinaten') + ax1.imshow(imgTrans) + ax1.set_xlabel('[px]') + ax1.set_ylabel('[px]') + ax1.scatter(path[:,0], path[:,1], color='r') + + #Bild Koordinaten + imgPlot2, ax2 = plt.subplots() + ax2.set_title('Bild Koordinaten') + ax2.set_xlabel('[px]') + ax2.set_ylabel('[px]') + ax2.imshow(img) + ax2.scatter(path[:,1], path[:,0], color='r') + +#%% Sphero Pfad + pathWeltX, pathWeltY = Map.calc_trans(path[:,1], path[:,0], height) \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__init__.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/GameWavefront.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/GameWavefront.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ece39f995948bca62bb749c1b4a8dab486ff1e6 GIT binary patch literal 8672 zcmd1j<>g{vU|=|4Ga==l4g<qu5C<7^FfcGUFfcF_uV7|iNMT4}%wdRv(2P-xU_Mh6 z6PRX>Vg}PJQ7mAZHHxiD0L+5uV9H_0WshQK1nFkZ;Rs;}XUOAbVMyhyG-hM~!Box^ zmS)B##&U*Kh6P*;85kK-SfjX8*iyNr8KQVn*ugY!3P%)QIztqHsz3^7GouSbtYVa4 zD$4?)RK64jNrqIu6fQ}Ig-lQpR<H;+NQ8+gN;pa+RX9~7g(00eN;H*!fmo{OLPkc0 zLa!8_DDf2DD2WuVR0$D=D9IGQD5+G5DCrdbD47)gC|QW9DFWgQDGX^$QF1AQV7JJp z@}zJ{Gejw*@`7nah#OM)B^aWVQiP(EQ-q>aQiP&ZQ-q?_Qq@y=ni*5oQ+b;i)0v_) zQW>%|7igtwEM$z*P7#jMNoB~=U7(l30CG_ZPn3R&NQ!6+LzF>^Sc-THLzH2PM2ch! zLzGd9REl&9LzHofOp0s^LzGF1T#9@PLzHQXLW*JwLzG#HQi^g5LzH=nN{VU=LzG2| zT8erLLzHETMv7(&LzGpDR*H5DLzFcqL#leJLaKbKk_dx11K4GXsq(4HA`B7?&5UVG zQ8rL{IZ(KwsF6!ym1KacvrV;2(Mh#wW@2PWwM)@WwFR@Z7ucujfc%nbl*-@C$jFe& zkmazzF;zQ7FV!|xJyjkQ(#?!13=3J2<n&WPwxY|iFK|lLUdR~boXVEvk_sYSQw(~U zqTJwS8>hOZ@;5U@xr0O11C(~aEYB1}up3NL-BWE+ZJU{6n4;XHyi)B_jKFR(1&ez% zGe>!+TBf?EdZ*enGc$rx5rZTHG#(kElv5a@R8owid{TX)d^s6Xtx}azl~Ywxy;FTt zeNyF`*<+Za{G$A$d{eE#GIGt#6Bvs&q=G{ZGbMsj5Vn+(r<`gA)~}N4*US>b6r~!a zmTH))nyLmB)o*5tVUALYQjStdwMgYp^-J{zyVe{c4`NSXENm`(u4(cTl!yH^8E<hG zmlmZZ78NB{YBJtp)T&Z&Eh@?{Qt-?z$S*3%%u81Ys7z1HP1T1hd&$Vaz>s-|m4PAi z1v3MKCd+e>Q49<m2Z~r37#LtYhaxrx28Nd@Ac6@*Ff%YPq|cfO0z2)~jltls{d0)O zN&9q&DGy+b5B4t?fOJ5F85lH~Uy3j=Fck5C%+g?BU`Pg;3X^4FU|;~*?hGo4(ij*R zN*J;j7ckW@WHF~OrZBZKNiw7`r?4z!u4O7=S-@JuRKu{4i4mfT4U`(d>e*`;L8*Zg zB+jvbb0I@5a}9GAR|$7EQ;~KGV=x1l$CJ%eWCY?B%4@P#>7`^A7vv;X7AqtwfWs&` zRY##bF|(vtAuYd1p|m))NTDQOAvr&<Br~rx)vpLtU>1SG@TCF+1A`{-Ew%!12;X9d znske!IJG1`H$Nry7He@vVnJ#V$fvisl5#Rj;=wU>izO{5v)~ptNL_MbK}lwQ-YwSD zvedkiTPz@}Z?TkA7Np+d_w^4Ac6AC33Gw%H@elXA#qRAI31Von+~Ncq6rY)!ev2h9 zF*o%VTXAwxYHHps#?+MzMe+;`48JP$GxBp&^-GgV^GZtfi}H(&;>(NmGxLf|5_59& ziwiPRi}K@hGxIXzL7pqh&&f$G($C3E(l4ki$;i($)-yEFFU~AU)h$R&&Q45EEyktC zJux>mJh3b_ttdaQM6aOo7B46uQ&MyD<BK!$%L71hD#XCRAjQPP$i>LR$i&FS$j8XX z$n>{J5tI->2@RGIxEUB2KzwjQxWmA}P{UBen9Woq3ySg@RuP60h6Ri@EDISy1y(Iv z4RZ|xRFnxW%3i}#%aP{;(#g$`%~WJq!vs=U%U;7$!&t*00nx!M%^=QD%aLbM!koe= z%>a@SXAl9?5)9G|wVWUkgxXrJ8m1b~5{3m#HC#0;;tUHJ!EBa=j1mlRw}9jsk>tTH z1<6Y=fNkdj*;vC?!(PLY#vII`$>aykZVV2ZEVo#46H78~F(>C2-(oJ#%)7-HuE~9i zsm$jVQ(4e0#)?~vmA6<+GEz$tZ!x8m++wN7OD(^}QVAkCz``IFW6&+Gl+>J(M6d`r zAwURFV!g#*P?VaInOqW(Uz`a_bupl*kzrt9;9wJD6k}v#<YHoBWMLFzlwcNMRA5wK z<Y44tDl!5^5-7#PA_<h9K@kOxq&eV7a$$(|tYxfWOktE{NMQoyH*k(*Dq&7xE@3HQ zEn%x+Okt7)2URm;2}=n(ScC;60;!KmSW4JRI7Aql8B>@*bw(i*+yqu~O-Nw~X3%6q z%Cnlx;H(QNg+K|j2vqVwMPF7hFfhFQ56ab?Mf?m547XTPiW19ju_b2~CFi6T$%2HK zGxJJrF_jwJV$8n9RAz9C*U2#`$kR0_$kD|!G+2}A7DrNIQBh`UQSmKyC<_t+pgdUw zjs+f&sZLN;WuPbkHPJX2c^I7-otRh{i|j!G4hm#gfP)H6b_NCpP#Iio#0bh2wM;dj z#?d5D5eE<SX(bFLj3rDp%!r__Whr4OVFrsa!vedL0TiI(47IE!3?)pU{L{?X$p~VX zuz(_<maUzkoiU9mg(Zcxg`<S6hNXj1f&oN|Gc+@~Ff=pPve&S?FvJGca@4TYaDZ%+ zV5sHnV5s3pVU}c&V5sFPVPC*e!&Sp2!cfCm!wJeE3mI#<Yq&cYvl)t})^OLbWwR7b zh3GBeOktK_0E>h5mvF)ODNHp?5H%2!Q<&12Z5UG6&_tQRqU>m*j3CiM=R%emreFq5 z4y0rNj`d{ZBn=8#P`M)viVhuc#Z|))D^$x^!qCA0@;!)z_&$~m6!tYtX-pHD3waBf zf+6LtCgUyEqQt!PR88hvOnL@InxNWFi-Cb*CDSdA<kY;9)S~#ZTWm#%DVe3kkR$<4 z4O})knZ+f^`MCvl?I6oR@vg&ArJRzPm{XDpO+u-~dNw)v$%#3|c6tc)MZ6$8j6n&O zyT}5>wgeGYAi^3%*nm<jKQx7U`nh@fIEJ_uftve8${;D0;=-bmTdd)pE+HPb*gRZ4 z-917yd2X?0=B8)n71@H6uw~|^=a-fg*?~APf0W*04svw4#SZa|(Ji)u{LH+P;#*7w zCAXN$N^dchmEGb<$t*5O%u7y%_|*yI9<Yb`(R@}1^0gWR0|N^qC=ZA*ihyYzMkOdM z#%RC@DxSm`SsGXvi$XxT0~BAd+`-4dzyQh};NnPt3DkH<VFWe*(iv)*(-~@6YM4MN zw3fAo36wf(*;1HlK%EqZR#3+VL_$&(3#cxsVS}bI)>`%&))Z!Oh8ngsCJ;*k%Bo=l zrB09-h$YUD#+1%d%TdGO0!lBODXcXNHJl*(YB)i*K}@S*Ucgwx32K}a+N7}6a+NTZ zFfRZVi=Y~ft%MDdKbje9xl7nn*lV~!xh!@;El&+Q$OX0RHO%P@*~~@XYM2*rEM%zV zt>KMfs%5QZ1Gx*k3qkIxVFkHR0@;P}wVY_~V@YF{WJux2W-fYzCc^}l0Tt#?Y8YyG z!G7X}`AL$Yh6fZ5*-S-GApR|31@jred}c|85^y+WL-?Qu97{F}OfCh&=E1blhM|T9 z7FIQ!;JW`!3TG`BBSRizVQ&pr3THM`(Wg8XNQe~YLPh>Ff?5hZX-vTkH7t-=FHEjs zr~!*;a`{!MB$a07q<|X4<*=rTjzU3BYGQFJxVfjGr>Ca?sT@F60;HM8$-uzi_7cno z<+2b>&ReVnMW7mhEwP{=H7^C6>uxdS6%@IH(zgeQ@B|T{Ja&t@AiqG9?-oa9ZaTQR zc8jC9B(bO@zO?KXXGvmFdMcR7l98HNaEm=JwH(Y~EWO28c8en?vA6`}(p#*#`DLla zw^%^bEtUdM6M-u?u_C@WHL)l;;}&;vX;D#X9z-oi0kmR=<T*r@!|R)v4Q`{kf!b)3 zKsn9_RP8f@s(TJ54n{F1K1LQM9YzjDF-9In7A7f1K1L2G1c^#97BGr2axrp%bucw> zFfuW6eCFU|V&q_E`Nzh{!B~`mo-aXFD5#kL%B#f&j0_B*{8+<S%aRVRd1{za7$q1| zm{ORVnOzuS+d-)r(zat{s9}j=s%5X`NN1?!OlPR&s$oxI6lbX6NMi!AB%rJs4v;8F z48#JZbC4`Z9ZVc#1Ew5ICCD6*iZrHl4p80X!Vs%n%TvR(fDx2(Qdq!g9Mqrz=Mhlu z#U@_Emd#XDQ^Sc{S7BfYQw?V`V<$s9LmFcWYYJNnM+tKc7if5+nX#4^+@;}N$XLr) z!(GEC$xzEu$XCM}%%I8c2d=5WiSgzC|NsAk6DUT-1xldciVM_~OJ}HIh~<i5s%5NY zs$uG2$Yv^Hs$rbS1h1&TX+x8_$PrYcGiDYgfa)m5TkN2&KyGO+SQ^|=DRO0CU^op* zE1)__hM`KvEi*4AAh9F^TV<rlRs>4)Md2W0nR7GqZgHmOmFA`vC6=Uus=d6F{M=h? z$r<^X$*G$BMNyz!5)C3i-Mw2}B}Iu@smUezMU}VM@=_~G3QCGVeNRwxDmfdJ5sKnL zib0OO#a5P>Q<_?Qi@7W@rzi;|oD8y*Ju@#QGdZ;wVnAjJ#3$Jxd2q$U0dxKyke?tm z4k-PLz|(aKBL|}lqZFeIBPfM~(mNL;3u6(eU6>3CWLS+Of!s@325OHngW6!VERdF0 zEo%yMEgPsURs(ME*0Pr{l`z&Yf`(MUEhctQzk-{gh67Ye*K&gD0!SHJ!wE8HAyX|^ z4Ob151Vb%%32O~^Gb6aX46NZ!Va#SJa;afUVU=d6<*8xgW&pEzYj`0g9bAM5ss<qg zDv#Nu8A_NJu-7nw`cD!JwR|;<C2T1WQ4WZxI72Nz*yVhnI;mCw>~emv%LPlA7jV`H z)UZe}Ku8gW8o?S?2`C9tzmTz3s0PC(afVu9Oq(PaYDK^{34?Ujih^wt0ox=7wn-Fh z6POfXs1XC(1SUc17c$n0r!b|k*06~%h%lru)d<yygL-tJaDj=}@PPVWpxP1=I-vXn zYV<)e6{yjj9?IE$^UIxu_TVg5qy%b8aTTGpqQFfh*2I#;JfmC8iAlvpprliz3QAWh zAOckMYf2y*d_~%z)W}huSP5<x-C`~;C<PH^x0uRGP?9k?!QbL81Jj9l={c#lm<x(C zi$LQeMd~0^n9Fl2ZZVhVRNi7P1+mI<DsC~Q<lJH|F9L}dfy6<qvLcXp5hTeY%6%^P z)Dq|Xypp2)oC}~t4{92Miggwy9!4HUAxKk;2_(YB$RWb@j)RdyQ0yb93gBT>V-#b8 zu_TxT7&(}VdeDn_P}2)EAOdcBnJ|KyUL}msM9&P4P)MR@DPhiHS-@Jtmcm%VUc=lB zB3VGAkR|LTjL5yd8U|2jK$@Y1YXLVTK}s;xveq!dMR_2i;tbFX#tKRd&<w@~PCOhb zETAj}wU4!gy@nlZ9|z1nP<N!6aUlyM1Ey`_pvnfrHgHB{C}9M3XT%w5dB8s72HVC9 z_Zi409<WWkm^PL0F5oNS2W2%-HB!QyC9pscG;Rg%p|bm}WG*rVB_#yG3F3m%<MV2f zb?y(Vme@mkso*LAtokKr#7t8NJ-LFa-6AuPc5@H`>a{|eDcnK+PW~Z~jw&P#gHkpk zA>U#t$;<@}TIHms78HRxK}7-}6F}p3ntW*K8<CESnm~%0K|~9PXa%KP&TtP`S05)I zf2Sf)ucoL2B-05Zx<CXtg@Ori$;{)JmzkSbk{Xnslwa}%lu$v!JWM={8jNDhkVMFW zk|6n*_!x_(peILA84b$T;N-XoJdC)2v4jZ{<TVVSK0ytW3qveNEprLO0#Iqq2pUE# z<St>&Vq3so!jQsL!coFm!d1cz>IbJVg9ajy<Ty)sO1MhEZD3HcYi2CrEa3x-AP;2V z($8ALS;GiwKZEquFoN0_;Do}N!Ujo>(8N&G3#yT!DFW27fkZjH<kEynfd#-RrXN&p z@k0hESkf|ca&ECE<s>F&gGUl>v4O@J6HCBD8b}EPH24Ef7)78`p=bgqUO*+^EjCEo zv<TFN)MSBl1KB~PS!N!jQZ53IoPeVbOn@TmmLN0^{SwPE(-T3XOFWDW48@=l0@Q5e zVB%wxVRB&9V=S7D9&Mm>4C;J?qm2nvoq~oolxvw&Kn*c))e5OtQ@~yN6wsg_vM8kc zhAzqi?*5~9^%y~&ngt9CAr&VJT1OBpTf-vFfGKW+MNE<bmb}0&Wdc>+kj^HOOOZtp zE=3jvyR?M4h9QLwG`9jv2H^MwC24SO2iH)LQT7M<4!^|VBd!Xd7MVgoPGVkOW?s4` z%Pr>I#EM&NiN&BnWJm;qhFLXPA#E;DtEvdx@B+o+E$-sv#GKT$L{QTUz4qXSMk&Yw zaZqH6f%^=gzPJRV9J3Ci476X)$5aFw>`w+IR#+PeG-d^gSa77)FfcHrfJTj&Ygi;1 zYB|95KBR5L2`)?_)dUx~B?KN%s^OGiNMRNRjVOWI;am_NXxtUlQcq#cW+<|(VFiuE z*0R*Fl(2#-m28$GO9(54MFQ0C=cr+?VF8b{viZ3|auKMug4F+@5>}H9z4TNDWdl%+ zev3ILHBXZVz1RZ<-z{EHZU7B=c;=;~R@@STaf4F}K<(Yk{Jf%>pi+>vD784Xq^KHX zCZhSu2OAlQFUgNDFTe<O_tcVbXxl*p6zZVz7cw>is+2*MHK_9DVk%mOo{>O_1)h;W zRRDOjoB`C+0*%V0FbRXkx51+zpq`g712{aHYnW45vKfjbQ&@u;QrIB<4shZ_1Xdpd z14D?LCKGtv2;AA#WI+lVuy1)l{wm3bmLA}e0Zf342QJTI$C40mr`HtZDUi`>knSW? z(Fz6zhAJ*_=U5@L*h-;_D?L9kM*++(nhGl1phX_Iv;#Q}G}@K|PT-*N=PK2LqSRv0 z6i#+(C1|<^VpLvYSt7J7f^_de<7Juw;DYcLQ+_eTGZR6vlMjxrywco)%AyF60I17z zix)DrqX!v!yTuKeRRhnk-D1hi%PhIY8tm-o<5~po_}>x;@NtZE^K|ubarO`La}5dx zm!FWXQ5MJ&_R_q}wEUvnTTDq6x0sSD!6R$nUQ-chxDHYdgIbukID#GBTwOeaLyFu$ z8o7NPBf?yL{GB~RB5(2gIz~7;JG=V0204a!`ul;?8>AoymEqvB8!3K3EmClU64JB; zjTPQvDbB1)h1BqZARmJp3e!P0vF7HN7N<hyr^+&mGm~;sA<Y3$IRc&z09PzUppiIm zw;$X?0=Evqbuzew2bT!oBnpmRNRb1sbHYHjO#>02Bw7S&rQBi%50DtA+~Ug2O@~Zn zS=?d;3*KTY&CDw?G`qzOmohYilx3itQ6vg73Dk)McNmKngSd-81UTC*0kOcz5J7+w zB!^9IeoARhsvRgf6>ni+V9*c%k7_W2au*LH2P4ye4rUf64rUHU7G@S^9wsgD>=g?W z3rGYs4#LF3D8K|7J>g*F0Z*K;fJJy1Sr~bkS(rJPIe0jvI5;>&IJ7uKI8-<!IJh|k zIe3{k6c{xaxj>K^)Zdh0<Y5#5O|^i{7hvXK<Y5LuP~Q}UnV6-R_?SVez&u7F7736% JBg<>1I{;fbUk3mH literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/WavefrontChatGpt.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/WavefrontChatGpt.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa1580cd086f8ded4733863dfce1d64fad32f881 GIT binary patch literal 2682 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 CV3y7R literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/WavefrontPlanner.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/WavefrontPlanner.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..69ecb8c18f678b45bcd6677f7b077115e6a96516 GIT binary patch literal 8584 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 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/__init__.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ec9298825be9e809c9848ee863fea5c84f958e7f GIT binary patch literal 208 zcmd1j<>g{vU|?`?cT54%k3j@7W&}wxFfbIeFfcHrFa$GbGWxA#C;|y1h+oC}8Tq-X z`lU&wc_pR#Mft@>@#V$(nR&$}i8(p?#RVCuMfvf$nR%J<$@zIDMfo{7sYUuZnMwKu zl_eSZdB%E%2KvRBC8@dviOJcC>8ZuI^u)(!=4F<|$LkeT-r}&y%}*)KNwotxrkII= Ifq{hq0410>zyJUM literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/_constants.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/_constants.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7445d8092b0df51dc16b0e6b2f448a1c3754da6b GIT binary patch literal 2315 zcmd1j<>g{vU|?`?cT9Q1%fRp$#6iX&SqBCNhT=&K3=Am@Q4A@JDNHHMDJ&_hDQqe1 zDI6)BDO__nqZm`TQ+QH%Q<ziuQutE@=CDOEr3j`7r3lYqiegR?NfFItjABXQNfApC zPmxHGoWl~u3f3W&BAp_W!k8kSBAX&Nha-v&Y>Ie_Sc-g#LW<%XwkY-#?i8^UrWC#u z<`ktA<rLNwg%p)JoKYMps=*AJYKi-p85p>{^YY8{6arF<G7B<NixP7bLPI@Wiggt7 zlS&dZ^HNh3k}4GnOH+#~GxO3FN{c~cNk*!IQ%-4WL8U@&eoARhsuh<lH2{}mUP-2c zi+`|!b4FrOVsc4pQD$*TX0n2ffq{XMfq{XkuBo|6nyyKjk%6wMvAKb+nT46Dxsh3# zk)e^H9hXyLNl9u^rGigtS!xc387Q)LTu43+C`wIBEh<V)QE<-B%S%lz$;{7F2uLhS z%uOvxEh@$^iFmDcT#f;r3SmYVMi>|v7=k>7-$O2Lp&%!f6y;-j2c#Q>@tfe6n45}W zxl3wUW^$?mNY0MSv7jI|u_!Sw8B-lX%8m=<6wkc0d<?@tZZI$~Fv0JR;M5|poz5AF zdFh~lMvGuHc{{GqylhayG{j|}F@F1?+HnRv0qtBN8L0|Isl_D<`DqHE<X)DTQ<_?= zP@Gtnnxc@I2Np}o1gDh5qRiA{g{0J+{Bj+I^3<YKg{1tVqWtpI6os^+{9G=F%=D!E zg3MI)Vuh^4vc%+~%z_eyoXn)6#G*<ah4PHd<P3$(Vui%A#LS$;q?}ZR#1e&!l9GaA zD}DX+%#w`KB)#PPTz#l1`o*AZnqOw5msQNg^-_R=f#Ibfh!6%5Vjw~sL`Z-LNf03g zBIFqu7&JL=@gx>x1{CEdrxq9I7v172t}HG|%>_m4EzW}c^3)<QlRc#<vn(?&{T5GR zUS@7$Nl|`YW^(Z@w&K)0kS@;R{M^)%43Gl0(&E%2Pybsya3RM4&zGP=@FgS2RyGhJ z1R^v*_Osm*%T3J8b1W#x$xKcJ<+8BUqT<Z_yjy}v`S~R|`H3m1MNpw4kk4*$!3{P- zVw)kc&0qF{%)bFLpXnBFa7kiOiGNx^VsdtB$t`Zzyc9TxJuxT8$JM3yB_Buw6Nq2| z5gZ_`EVsBai-S^&3-a@dQ*Q|rr52W^7MCDIBw-@{c{!D?MMe2VaK++9sl}-!#h!VI z$t9U(nI)AWnYpR?r6n(!L8kM92v!gw2eOCb7H3LoYC&*LYHGnP)?zTlmjo%d!t!%U z64O&}v6Lrfr-J=o1d2N(&l)4KO<uBtOyvQY%65yRD6!l(za+ot77y4t9;t~bpcvu> z#bv5vaWFX5+~S0CgOYCvfSKW$B^hwF#U+VJnK_wN;HY^i0y3T-WIX3BE=b(_CKhMk z;)Af^{ufHl&r8cpFD*)S&d<ro1jnaXGK}exT9TSvlA7XJT$z^)b(~Lraw5p7BB^<x z1P|8+QNs=~-|!ZcVRTCrZbL9Q2?m!Gr6%V3X>#6TkB?8uPmYhj#gPIo(>z^n2?fIw zwR3)MZem_a@h#!v)Vvf(nsCm~D=taQD=EGu07<-1MV>Cjw^-A164Q%s@q@Dhk~lvo zs50}?p+d#5Ab{~#G86?cFhGc3rTQ89xvBc4Nu_xurTRtr#YXYv#rl~bTXS-txi>yH zGcPkfIX@3nvgf20>E~o7=@(R%WaQ@=>lqs87iX5F>J}s>XD6no7UR+b(hu>9UP0w8 z4jV{SYzNAC#aRpt3@i*xB20{^knIN(7ea*XHxoM(BinxtW+o<xDBE8qRtO75vi)S@ Qgt3_zAvzFz5R2;v0L&rbi2wiq literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/a_star.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/a_star.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..08bd874229860af63cd5c26676a69f05c506eb88 GIT binary patch literal 7276 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 M5zb-2#mMp>0DrlI1ONa4 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/blobErkennung.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/blobErkennung.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a67c7471a3ce7a50008d13c5d01bf261017add2 GIT binary patch literal 1182 zcmd1j<>g{vU|>*7SeByA&cN^(#6iaF3=9ko3=9m#T#O71DGVu$ISf${nlXwgm0<yM z3e!TyD3(-)1*{7hqS#Uy7O*d5h~h|LPUVQ_Okqjoi01;cxl>qEIpTRz*it#-dBGxl zDeS2n@%*U_3j|U*7BWV$rLrs#T*wf^6eScToGP3plERq6(aIzV5)q9OOW{o6YGH^H z=VU-p$FM*mRXCL)OL8GolvFC)0_jw#g^W=$sWL7Mu{=?-sj^veDGZVfQSx9Gw<JT9 zLSc9+!ve)rsZ^O%rUgpr3=0`kcv2a%lozr_siZP2P)*@YRawZy$dJmgKrMxDA!7=Q z14F8M3V$<WHd9ec3S%%sp|hsIOHe5Jy<}lvU<i5npMinlr3i>P!N9<f4w7PEcwqmM ziGhJ38AL!t7#KjxL40RWkb*RnFw`(gFw`>DFw`>FFxN1pF$FVdGWuyU-eSv0%}mcI z(PX*BSaFN7@)lcVNKs;5@h!FrFujtYh=+lJ;a7!zMt*LperZx^UP-BbQGT&ee0i~c zW?pegVor{JaY06EQGR@GW?p7|a(-S(QGQNNYLR|UW|DqEWl2VUp0S>xfqrpjNvdu^ zVsdt3dTKE)JxMwFNv=iNsd;&&dFgrumAAN(6LXT|OF)h?0(pmxfq{X8k%f_isfZ60 z6pRcE44TYEAPPb-GcYjR;`C0fEXdEyD=B^nN_n>!3pF)vF(;Q9-D1rv%`K?B#gtcY zizh5IB{knUv7n^1DD@U|a$><Pq2SEif}B*Roctu0)RNTXlKi4u94RoCB&KY9Kw?p1 zZt*R)0tn5UmYGwMTI5veoS&0lbc++@9|)5NCgWI?ns|#nH!}~)NUQ)e_+j#%d8tJu znTfafb2Iax?4ZPw%=}x}tWPdVO)N>h#Zr`-m~xA~xHLIAwYc~eb7pS(E!N_U#Ddf! zP~N)5TArCwk^v5}TO7${B@id^Is5zg2gL`uI~lnLIY!=MNiRyQyu}9b?=8-3SQy^o z$WE;c00rkQrh<|p4h9B>B2G{+^QIIfmcxVk7HeV=$N)8$Ajj}{U&jz<57*#$Hy=m$ z;CQf5kf*apytiv)fWN0-NboJe%-s0$%#w_Fghgzbxy2dz<+s?&6EjP^Q!8&V=NF}b zlOPho3$mKSCO1E&G$+*#6!XOvpj5%k!N|qJ!OX$T#?Hpb#>~dZ#>mFY#>mCY!z9GW e!NkPI!^FqP#mvFX#mK`b#m>Vl#>B$N^a22ZfK!41 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/core.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/core.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f0d8bf82de3f38133e759720cfe87e1591b9d40 GIT binary patch literal 18927 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 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/dynamicPathPlanning.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/dynamicPathPlanning.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..783f7aad7b95b157db735ab8cb5f819656e8d116 GIT binary patch literal 12309 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 b(6FosxNL^7K#OU3APZ}lBp78t^}!SX+<{^@ literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/map.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/map.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..620dd53b562bd7e966028c25d4cf45bea0a723f2 GIT binary patch literal 600 zcmd1j<>g{vU|?YTTAFf}k%8ech=YvT85kHG7#J9eXD~1@q%fo~<}gG-XvQd}RE7o2 zDNG9)qgYZ|7O*a4h+>1#F-%eHQEaIU3pi5QQW>&17cxb0r7|qwPUT8vTELUSoX)tA z5zJx%u@*8%@usk*@}}~puw*mYFr+eN@uc#mGG+0lu=O%6;7?&+$hbgYAwv{z3I|vf zCs>tWDsM9*BSQ*fFoP!7OOTuWsthwqN(!v>^^5ZJORPYIgQ0<*p^=52iJ_j6v89!T zfqrgQLArifW=d+l9*Freih+UQ<$n+n&cMK+$#{!3rKGYT^%hgG(JjXCVkQO#hVXDr zp<B$!Wk$DH^Gb6IDsM6672M(p1DWBRSWr@0lzNLfIkDgtOHpcK$}Q&1-1J-Q#ihx~ zsl~;&IFidsobz+?i*E5b`}_C@#Rs`N8My~JM&4pcFG{Su#h#fLl$e*EdW$u&2*hW} zO)Sp7#afz~S7LFCr7SbG{1$t0VQFe=Rq8F)f}+g4l9dcaQVa|b;#Z!2Mt*LperZx^ zUP-BbQGT&ee0i~cW?pegVor{JaY06EQGR@GW?p7|a(-S(QGQNNYLR|UW|DqEWl2VU zp0S>xfqrpjNvdu^Vsdt3dTKE)J-LYmdIgoYIBatBQ%ZAE?HEC6LX3fdfrFWYk&Bsw TiHDhwQI3&^Ns5VwiH8XQ_NcI6 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/maperstellen.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/maperstellen.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d7213af60b65c5c312c56bbf5e7092116999e2e1 GIT binary patch literal 212 zcmd1j<>g{vU|{&n7nlN~AA<;F%*epN;K0DZP|U)>z>vZa%%I8Wx00a<B#a<_mFs8Z z=cekHCY9!ul<F7d7aPTw7wc!{6_+ID<meX{WTY15$LD6|WyUAx=am%Y=j5ao>E~o7 z=@(R%WaQ@=>lqs87iX5F>J}s>XD6no7UR;Bn^=%qR9upplardKS5SG2!zMRBr8Fni O4&<O>CI$uu76t&7d^;)t literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node copy.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node copy.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff02a86b8c9265a9299da6733a1150a03c43a61e GIT binary patch literal 4396 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 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..248bb73e7affc7258d67ea37f5b04afb3cfde55b GIT binary patch literal 16367 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 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_alt.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_alt.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..06a366f1ce4b99ca1ddaf00017abd0a8c6868050 GIT binary patch literal 7256 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 F{{SJtx#R!< literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_komisch.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_komisch.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c1e46cdc304f86d99a14af9101ff3d91dc2a4b89 GIT binary patch literal 6049 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 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_refwinkel.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_refwinkel.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e43bbb000175664ea4762646b70442df7a02e31f GIT binary patch literal 5129 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 Qm>BB0tT@!U7+L-U0D(RHXaE2J literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_testfahrt.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/my_first_node_testfahrt.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..76054ad895508d64e1faf9ec5c48338bd50f956e GIT binary patch literal 4401 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 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/pfadplanung.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/pfadplanung.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c01561f4863ce4dd3f66016afa225f0416bf5b5 GIT binary patch literal 5569 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 bo1apelWGTQI~9Y9Q4U6idX^X#Mm|OWJQPa{ literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/potential_field_planning (1).cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/potential_field_planning (1).cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f6164b8357f6cfb2c910cd87d914c0b16dcb959 GIT binary patch literal 4688 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 M9!54s0Y;X;0OU|V$N&HU literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/potential_field_planning.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/potential_field_planning.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89654d357ee99d891520683d490f3272debfb4a9 GIT binary patch literal 5668 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 AEdT%j literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/simple_moves.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/simple_moves.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..650d674336f70da01d14cf5aebb304a70229227c GIT binary patch literal 824 zcmd1j<>g{vU|>+LbW54d!octt#6iYP3=9ko3=9m#5)2FsDGX5zDU2yhIgC+^Da<J> zEeugiDNMl(nygjTT)~;S1v#k-x%p+O#ay~bfQu_6BePf`H$SB`CsiRiKd&S)Gp|@7 ztu!yWBr`v+SfM0eAtSLYRiPv!RUx<_Bef`BAvZHGQz0c^p*TM`RiPMRa&Br~NwI=P zs$RODLULwNa!#tI9@k5dgZ+{jK@=2&*sKf;49*~@A7fx(sA0$gIh_&Y{9a~8h8l(i zj48|u8Nn>36c#9pIfWI<Vp+%lmSap|gNiVvFhW>RlUP!ipeC`Vu%|FCWCE*4n82FC zh$PB^q?ZNB6|5<oNUC7AW6_(!5X_*-<yQpqnkMrt=AzV;Tde6tsi}FlSdwx|Q{7(v zXJBA>$;`mO5b`nw#006#ob&(xe@(Vq+{LLSKCUj#`8oMTw|I+75|c7>GOH3n5pYYm zIJLwnF*)1E)x|TfBsH%%v!wDCOL<~u$t|9u)Z)|<kJQAJ%)InlEJgV_Ihu^O*owii zw34AnfPsPGSGj&her~FMX;NukNvVEOez8$}d9i+GUU5lcPL4iAZ9FJO<3W)RG9$G} zKPNLuzo4=tBR|hr&(J`>II|>Gw;(Y&J25@A7?&PMLWl<^F};GyTkL81MdgV_DWD`= zti-^;Ai%`M2!b47Qh=F<k&B6kiHlKyiHDJkk%x(ou}Fx4fkBh`7JGbrN`7*D{4M6< l%3`n}*e^(gAOi!#Ee@MpaI&=ng=H}t0|NsK0|z?~69Bm3*a-jt literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/sphero.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/sphero.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c18e5dffcef2b5c7adb3e92614aaaa2715e23296 GIT binary patch literal 647 zcmd1j<>g{vU|>-H5s>njk%8ech=Yuo7#J8F7#J9eMHm<uQW#Pga~Pr+Q<zeiTNt94 zQW%37G+ADP)cYlaRDxI_%+A2T0K(27o%0wN7-|@@7;2en7#1+5Fs3jqWMX8fVOYSF z!n}|X%mSH+5M_agW--(<gH=KK1obl4Fi9}fveYnwY1R_v1uQizHLM~GCCpi@3)pH{ z7BWIvtP2@M7^E2%GS;%yumv+{viiMDVPIe=Vq{=ocnR`II>;9c3=ixz*>4FJ<QHd_ zWaj4;CzclHq!y><-C`-q&&j#PQl6Mua*HXi;1*kAQF3yA@h!HT%)G>$^jj=>`9-;! z?6=s83o=rR@^7&egVb{smn0UI6qM#=m)v45N=>`P2^NS4F(GpCV0Fv|8JQ~?ii8*# z7=9J%XXNLm>X#;!=9QG{7v&cl#g`ZBXXX``B<AGkLyV8l&CJV;PtMOP0XZwRNIxet zNxz`7BqKl1SkKTvzc{lbRkt89IXf{uwHTKkh*5e4mAANxQq$tgGxM@jb3j2+EW^OS zz{bSG#Kp+N$i>LU#Ki=nc^EmEI2hF!Ss05zNkfzQ7HeK<Zb4-cDBi(BU=JV>!VC-y aw>WHa^HWN5Qtd!NUChG3z`()4!w3LvBc)vc literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/sphero_main.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/sphero_main.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d95a51ee66ceb429dcd3290ef44c9288bc375fdb GIT binary patch literal 3471 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 M7&#i4{;~W40PWr{761SM literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/test.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/test.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1652f1531f8f1fa2134712bba5ab5118856c0730 GIT binary patch literal 767 zcmd1j<>g{vU|=}<y)@+=BLl-@5C<8vGcYhXFfcF_Z(?9zNMT4}%wdRv(2P+`sSFF4 zQ<xSqMzN%_EMQ&85W^J37R8>*uz(|#J(VGgb0Jd{S1Q8-?o_T+rUg7H%;}5^8Nn<T z5NjcG6mJScI%6tV7GDahB!dWy%>ZIY@uxCn2`msy<z2`aC6p?h!jQ_F#h1b$$<WN? z!Vt?FC6dCH!rsD=DwHZB$>74!%$UlbDq_P>=$a~;%HPb$$dJMq%%I8f6671dD#MJD zk^(Dz{i6K*5-SklU}&IcXk?*hVyI_iY-wd-pr4ynkgi{rnUb2X2V%aAVqjo+`5#0u zGB7YCg9wmWAk4(Tz`(-5z~BrD85sr!hIED+hFGpz#u~;HhHRE1t`x>R#uTQBOodFr z44TX@PcSer++qy>#i+27L6hkglb*pX#;hU^1_lN&@he|HBR@A)zci^ducTDJD8JY! zzPwmJGq1QLF(*gAxF937C_g?oGcPkfIX|zYC_g7BwMaiFGfBUovLquv&sfjUK)*P% zBvrQ{F*!RiJ+&B@o|4q!61{@TTU<6dnZ+f^`MCvl!XQ6@JS4+V#KXYApec5XIl0W} z7HeK<Zb9WOro4h%JYk?vaZW5KDJ@F9#hjd2aEqlVH8JHDdvR%Ua%yq$E#}PJ^jjRs zWhKt}Ir&Am_?-QH{Db0y+?|ZvgB&Aov7{FzR^DRI%nM4)OHaMUnpgzlv*acgXWwEi z&PXgsy~UNASm2))o?n!ca*I7L1)_qjBp>9?Tbv-3@n9xv5!eXE%v-DlMVWae-~dD- uxIsb9VUwGmQks)$2a2>}F$M+(4kivpE@mDkJ|;Ov873Yk5he*n9wq>2#L{>G literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/treiber.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/treiber.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..371445741325a38df010cc651d7b73d397ff1efc GIT binary patch literal 18883 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 J^&lwb1pvP+0RR91 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/wavefront.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/wavefront.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..751df02c9c93c0c2ace4b207f86ae0998a1571c7 GIT binary patch literal 3571 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 E03*Nm1poj5 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/wavefront_coverage_path_planner.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/wavefront_coverage_path_planner.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bb64c53ebc91edff78d10e432020a92c12d0dd20 GIT binary patch literal 5730 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 literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/wavefrontgpt2.cpython-310.pyc b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/__pycache__/wavefrontgpt2.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..693e46d875943c27484df42e4d6294ef7faa6e5f GIT binary patch literal 1823 zcmd1j<>g{vU|{I7o{;jDi-F-Wh=Yt-85kHG7#J9egBTbXQW#Pga~N_NqZk<(QkYVh zTNt94(iu`%7BQwXq_BcX1e+y_IfX5XC51hTHI*rqIh7@qwV64JEtNfmBbBY0iIE|N zGnhe>>m|rGO~zZSfu*UXsmaVBE)?@IFff4Z2Z<FgVq#z@VW?qfW~^lf(J73Q49$$4 z3^j}(RtjS_L$MWvUBjHs3T87&GQh=|BpEswz~W3u>cMQNdZrYFdN3QLzR0eGF@?FA zv5B#rA&oJGC55$xqlBr3xtY-g<Pw&2h7`6%jJ2#K%*_n7Y&C2PSZbISf<lL}hNT8< zx+Fsl*fbl48ny+j3mJ;f*03#LTgXt$Uc>6b5F201QNs>$Q4I%(6lbVm2f3q$14N25 zq%qZUrZCoUg5+2jm>IGei`r|Lvl)s`<S{Wa6lQ|um?RmXa#K4P5OOsv5PgyiHJl*R zBpFiJvzdxc)PVSmk_<@Vj9_sVh)X1)ZjfY1VG?IRHwEkumKsiwFAFmvekw|?VS$9b zBq%<ZKxTvOx?94S!T}1s8pd?STCN(#TJ{?D8m4UK35-SlDGasTCG07j&5X4?;IQx} z7#6%Kj5WN7u$aJD#19IM35-Qn;P7}4mWPMO1jZtHP>3MpYgi!qB^hdXAz@O(lg$8< zui>rb#+0q)DSThUox)HGQsYv>n8F424c7vW8g5Vs)NsRmRQ$Y#E0{r(+pma`fq~&A zh^W#CE=epZQOGY+NY78qQ7FhS&Me8y&r`@OR>&_cQOHkINXjqGODWb%1}TT8dIly2 z1_l-e1_ohJwi9DuU`S`EVTfg~WlUj6XY61QVVKBN$P~<=$@udB|NsA2GH5d0V$w6X z#TdVm@fJ&Reok=_DAW9^(9g)vP1P?=D$Oe?)i25~Hi|DV*3Zl<E=kPE(JwB@NG-~b z&&|xsj8D$bD=Es)$w@8J&&f>EFQ_cZ$j>v@Gc?dI&MZmQEl5nxPE1cN#-*n`u`D&M zC_k?xy`aQMub}c4mrYJ)aY=H1Zh;*y$PXY7i7`~E!WG6B<Rs?hr55Sg<m4wO<`moM zA#`Z6XfhRnyblgXO{OAH?uW5K!337nWC9C8BtfdH<RD><9LA-2MX8C&8Hq_bsd~4V zGxO4_`26w}3KB~)6w*N9uBXX)i#aDX?-o~BVoqtQYf({t(Jj`Z#Ju#>B9NPJF&C7U z++t15EhwqH#hjj6a*H)BCqJ>|7F%LLL26!#Cf_a2+{A+T#G<0a%3GYppvaCd$S=Od z0g7%gi=`;Py!aL`!soe(1-H0TGK*4^K`Ej57Hc7>*t^A@Tv}9=npXnW#0i(b#h#a1 z4i;kpIr|nzUTOtI4Nq=nUVKVsaY<rca_TLfywuF}jHLV`kXmr`7jZB!Fcg8x_FIAo z+o9nO3ew^<1_lNWMjl23W-dmi|HznwNsO6;nT?r?QG}U;k%N(gS%J}lS&WhCKL~3u zaWOJAaD0{E<6vZCW?|%FWC5vRQebpo5@Tff&%z|a$kf2a#KFkL#KOq&fkmiDnSp^p zlLZoE;3$L>Fj0_;Adv}@g2gVB4U&8bBC1#e5=%0y{4_a=q(R;TMH1NXB2f?*lvj$x zK`eO&28LU#1x1;8C6Len<p>B193&hzx%nxjIjMFa8;U{tco;aC*zDODL6GGS3p)!V I4-+3F0K)dPiU0rr literal 0 HcmV?d00001 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/_constants.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/_constants.py new file mode 100644 index 0000000..fa8a0e8 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/_constants.py @@ -0,0 +1,72 @@ +''' +Known Peripheral UUIDs, obtained by querying using the Bluepy module: +===================================================================== +Anti DOS Characteristic <00020005-574f-4f20-5370-6865726f2121> +Battery Level Characteristic <Battery Level> +Peripheral Preferred Connection Parameters Characteristic <Peripheral Preferred Connection Parameters> +API V2 Characteristic <00010002-574f-4f20-5370-6865726f2121> +DFU Control Characteristic <00020002-574f-4f20-5370-6865726f2121> +Name Characteristic <Device Name> +Appearance Characteristic <Appearance> +DFU Info Characteristic <00020004-574f-4f20-5370-6865726f2121> +Service Changed Characteristic <Service Changed> +Unknown1 Characteristic <00020003-574f-4f20-5370-6865726f2121> +Unknown2 Characteristic <00010003-574f-4f20-5370-6865726f2121> + +The rest of the values saved in the dictionaries below, were borrowed from +@igbopie's javacript library, which is available at https://github.com/igbopie/spherov2.js + +''' + +deviceID = {"apiProcessor": 0x10, # 16 + "systemInfo": 0x11, # 17 + "powerInfo": 0x13, # 19 + "driving": 0x16, # 22 + "animatronics": 0x17, # 23 + "sensor": 0x18, # 24 + "something": 0x19, # 25 + "userIO": 0x1a, # 26 + "somethingAPI": 0x1f} # 31 + +SystemInfoCommands = {"mainApplicationVersion": 0x00, # 00 + "bootloaderVersion": 0x01, # 01 + "something": 0x06, # 06 + "something2": 0x13, # 19 + "something6": 0x12, # 18 + "something7": 0x28} # 40 + +sendPacketConstants = {"StartOfPacket": 0x8d, # 141 + "EndOfPacket": 0xd8} # 216 + +userIOCommandIDs = {"allLEDs": 0x0e} # 14 + +flags= {"isResponse": 0x01, # 0x01 + "requestsResponse": 0x02, # 0x02 + "requestsOnlyErrorResponse": 0x04, # 0x04 + "resetsInactivityTimeout": 0x08} # 0x08 + +powerCommandIDs={"deepSleep": 0x00, # 0 + "sleep": 0x01, # 01 + "batteryVoltage": 0x03, # 03 + "wake": 0x0D, # 13 + "something": 0x05, # 05 + "something2": 0x10, # 16 + "something3": 0x04, # 04 + "something4": 0x1E} # 30 + +drivingCommands={"rawMotor": 0x01, # 1 + "resetHeading": 0x06, # 6 + "driveAsSphero": 0x04, # 4 + "driveAsRc": 0x02, # 2 + "driveWithHeading": 0x07, # 7 + "stabilization": 0x0C} # 12 + +sensorCommands={'sensorMask': 0x00, # 00 + 'sensorResponse': 0x02, # 02 + 'configureCollision': 0x11, # 17 + 'collisionDetectedAsync': 0x12, # 18 + 'resetLocator': 0x13, # 19 + 'enableCollisionAsync': 0x14, # 20 + 'sensor1': 0x0F, # 15 + 'sensor2': 0x17, # 23 + 'configureSensorStream': 0x0C} # 12 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/a_star.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/a_star.py new file mode 100644 index 0000000..6d20350 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/a_star.py @@ -0,0 +1,282 @@ +""" + +A* grid planning + +author: Atsushi Sakai(@Atsushi_twi) + Nikos Kanargias (nkana@tee.gr) + +See Wikipedia article (https://en.wikipedia.org/wiki/A*_search_algorithm) + +""" + +import math + +import matplotlib.pyplot as plt + +show_animation = True + + +class AStarPlanner: + + def __init__(self, ox, oy, resolution, rr): + """ + Initialize grid map for a star planning + + ox: x position list of Obstacles [m] + oy: y position list of Obstacles [m] + resolution: grid resolution [m] + rr: robot radius[m] + """ + + self.resolution = resolution + self.rr = rr + self.min_x, self.min_y = 0, 0 + self.max_x, self.max_y = 0, 0 + self.obstacle_map = None + self.x_width, self.y_width = 0, 0 + self.motion = self.get_motion_model() + self.calc_obstacle_map(ox, oy) + + class Node: + def __init__(self, x, y, cost, parent_index): + self.x = x # index of grid + self.y = y # index of grid + self.cost = cost + self.parent_index = parent_index + + def __str__(self): + return str(self.x) + "," + str(self.y) + "," + str( + self.cost) + "," + str(self.parent_index) + + def planning(self, sx, sy, gx, gy): + """ + A star path search + + input: + s_x: start x position [m] + s_y: start y position [m] + gx: goal x position [m] + gy: goal y position [m] + + output: + rx: x position list of the final path + ry: y position list of the final path + """ + + start_node = self.Node(self.calc_xy_index(sx, self.min_x), + self.calc_xy_index(sy, self.min_y), 0.0, -1) + goal_node = self.Node(self.calc_xy_index(gx, self.min_x), + self.calc_xy_index(gy, self.min_y), 0.0, -1) + + open_set, closed_set = dict(), dict() + open_set[self.calc_grid_index(start_node)] = start_node + + while True: + if len(open_set) == 0: + print("Open set is empty..") + break + + c_id = min( + open_set, + key=lambda o: open_set[o].cost + self.calc_heuristic(goal_node, + open_set[ + o])) + current = open_set[c_id] + + # show graph + if show_animation: # pragma: no cover + plt.plot(self.calc_grid_position(current.x, self.min_x), + self.calc_grid_position(current.y, self.min_y), "xc") + # for stopping simulation with the esc key. + plt.gcf().canvas.mpl_connect('key_release_event', + lambda event: [exit( + 0) if event.key == 'escape' else None]) + if len(closed_set.keys()) % 10 == 0: + plt.pause(0.001) + + if current.x == goal_node.x and current.y == goal_node.y: + print("Find goal") + goal_node.parent_index = current.parent_index + goal_node.cost = current.cost + break + + # Remove the item from the open set + del open_set[c_id] + + # Add it to the closed set + closed_set[c_id] = current + + # expand_grid search grid based on motion model + for i, _ in enumerate(self.motion): + node = self.Node(current.x + self.motion[i][0], + current.y + self.motion[i][1], + current.cost + self.motion[i][2], c_id) + n_id = self.calc_grid_index(node) + + # If the node is not safe, do nothing + if not self.verify_node(node): + continue + + if n_id in closed_set: + continue + + if n_id not in open_set: + open_set[n_id] = node # discovered a new node + else: + if open_set[n_id].cost > node.cost: + # This path is the best until now. record it + open_set[n_id] = node + + rx, ry = self.calc_final_path(goal_node, closed_set) + + return rx, ry + + def calc_final_path(self, goal_node, closed_set): + # generate final course + rx, ry = [self.calc_grid_position(goal_node.x, self.min_x)], [ + self.calc_grid_position(goal_node.y, self.min_y)] + parent_index = goal_node.parent_index + while parent_index != -1: + n = closed_set[parent_index] + rx.append(self.calc_grid_position(n.x, self.min_x)) + ry.append(self.calc_grid_position(n.y, self.min_y)) + parent_index = n.parent_index + + return rx, ry + + @staticmethod + def calc_heuristic(n1, n2): + w = 1.0 # weight of heuristic + d = w * math.hypot(n1.x - n2.x, n1.y - n2.y) + return d + + def calc_grid_position(self, index, min_position): + """ + calc grid position + + :param index: + :param min_position: + :return: + """ + pos = index * self.resolution + min_position + return pos + + def calc_xy_index(self, position, min_pos): + return round((position - min_pos) / self.resolution) + + def calc_grid_index(self, node): + return (node.y - self.min_y) * self.x_width + (node.x - self.min_x) + + def verify_node(self, node): + px = self.calc_grid_position(node.x, self.min_x) + py = self.calc_grid_position(node.y, self.min_y) + + if px < self.min_x: + return False + elif py < self.min_y: + return False + elif px >= self.max_x: + return False + elif py >= self.max_y: + return False + + # collision check + if self.obstacle_map[node.x][node.y]: + return False + + return True + + def calc_obstacle_map(self, ox, oy): + + self.min_x = round(min(ox)) + self.min_y = round(min(oy)) + self.max_x = round(max(ox)) + self.max_y = round(max(oy)) + print("min_x:", self.min_x) + print("min_y:", self.min_y) + print("max_x:", self.max_x) + print("max_y:", self.max_y) + + self.x_width = round((self.max_x - self.min_x) / self.resolution) + self.y_width = round((self.max_y - self.min_y) / self.resolution) + print("x_width:", self.x_width) + print("y_width:", self.y_width) + + # obstacle map generation + self.obstacle_map = [[False for _ in range(self.y_width)] + for _ in range(self.x_width)] + for ix in range(self.x_width): + x = self.calc_grid_position(ix, self.min_x) + for iy in range(self.y_width): + y = self.calc_grid_position(iy, self.min_y) + for iox, ioy in zip(ox, oy): + d = math.hypot(iox - x, ioy - y) + if d <= self.rr: + self.obstacle_map[ix][iy] = True + break + + @staticmethod + def get_motion_model(): + # dx, dy, cost + motion = [[1, 0, 1], + [0, 1, 1], + [-1, 0, 1], + [0, -1, 1], + [-1, -1, math.sqrt(2)], + [-1, 1, math.sqrt(2)], + [1, -1, math.sqrt(2)], + [1, 1, math.sqrt(2)]] + + return motion + + +def main(): + print(__file__ + " start!!") + + # start and goal position + sx = 10.0 # [m] + sy = 10.0 # [m] + gx = 50.0 # [m] + gy = 50.0 # [m] + grid_size = 2.0 # [m] + robot_radius = 1.0 # [m] + + # set obstacle positions + ox, oy = [], [] + for i in range(-10, 60): + ox.append(i) + oy.append(-10.0) + for i in range(-10, 60): + ox.append(60.0) + oy.append(i) + for i in range(-10, 61): + ox.append(i) + oy.append(60.0) + for i in range(-10, 61): + ox.append(-10.0) + oy.append(i) + for i in range(-10, 40): + ox.append(20.0) + oy.append(i) + for i in range(0, 40): + ox.append(40.0) + oy.append(60.0 - i) + + if show_animation: # pragma: no cover + plt.plot(ox, oy, ".k") + plt.plot(sx, sy, "og") + plt.plot(gx, gy, "xb") + plt.grid(True) + plt.axis("equal") + + a_star = AStarPlanner(ox, oy, grid_size, robot_radius) + rx, ry = a_star.planning(sx, sy, gx, gy) + + if show_animation: # pragma: no cover + plt.plot(rx, ry, "-r") + plt.pause(0.001) + plt.show() + + +if __name__ == '__main__': + main() diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/blobErkennung.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/blobErkennung.py new file mode 100644 index 0000000..20036f5 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/blobErkennung.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 + +# Standard imports +import cv2 +import numpy as np; + +# Img Objekt +#cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") +cap = cv2.VideoCapture(4) + +# Set up the detector with default parameters. +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +params.filterByArea = True +params.minArea = 20 +params.maxArea = 200 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +params.filterByInertia = True +params.minInertiaRatio = 0.5 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) + +success, img = cap.read() +height, width = img.shape[:2] + +def calc_trans(x,y): + yTrans = height - y + xTrans = x + + return xTrans, yTrans + +while True: + + success, img = cap.read() + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + + # Detect blobs. + keypoints = detector.detect(gray) + + #print(keypoints) + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + #s = keyPoint.sizekeyPoints + + # Draw detected blobs as red circles. + # cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures + # the size of the circle corresponds to the size of blob + + im_with_keypoints = cv2.drawKeypoints(gray, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) + + # Show blobs + cv2.imshow("Keypoints", im_with_keypoints) + + if cv2.waitKey(10) & 0xFF == ord('q'): + break \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/core.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/core.py new file mode 100644 index 0000000..e8fecdc --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/core.py @@ -0,0 +1,638 @@ +from bluepy.btle import Peripheral +from bluepy import btle +from _constants import * +import struct +import time +import sys + +class SpheroMini(): + def __init__(self, MACAddr, verbosity = 4, user_delegate = None): + ''' + initialize class instance and then build collect BLE sevices and characteristics. + Also sends text string to Anti-DOS characteristic to prevent returning to sleep, + and initializes notifications (which is what the sphero uses to send data back to + the client). + ''' + self.verbosity = verbosity # 0 = Silent, + # 1 = Connection/disconnection only + # 2 = Init messages + # 3 = Recieved commands + # 4 = Acknowledgements + self.sequence = 1 + self.v_batt = None # will be updated with battery voltage when sphero.getBatteryVoltage() is called + self.firmware_version = [] # will be updated with firware version when sphero.returnMainApplicationVersion() is called + + if self.verbosity > 0: + print("[INFO] Connecting to %s", MACAddr) + self.p = Peripheral(MACAddr, "random") #connect + + if self.verbosity > 1: + print("[INIT] Initializing") + + # Subscribe to notifications + self.sphero_delegate = MyDelegate(self, user_delegate) # Pass a reference to this instance when initializing + self.p.setDelegate(self.sphero_delegate) + + if self.verbosity > 1: + print("[INIT] Read all characteristics and descriptors") + # Get characteristics and descriptors + self.API_V2_characteristic = self.p.getCharacteristics(uuid="00010002-574f-4f20-5370-6865726f2121")[0] + self.AntiDOS_characteristic = self.p.getCharacteristics(uuid="00020005-574f-4f20-5370-6865726f2121")[0] + self.DFU_characteristic = self.p.getCharacteristics(uuid="00020002-574f-4f20-5370-6865726f2121")[0] + self.DFU2_characteristic = self.p.getCharacteristics(uuid="00020004-574f-4f20-5370-6865726f2121")[0] + self.API_descriptor = self.API_V2_characteristic.getDescriptors(forUUID=0x2902)[0] + self.DFU_descriptor = self.DFU_characteristic.getDescriptors(forUUID=0x2902)[0] + + # The rest of this sequence was observed during bluetooth sniffing: + # Unlock code: prevent the sphero mini from going to sleep again after 10 seconds + if self.verbosity > 1: + print("[INIT] Writing AntiDOS characteristic unlock code") + self.AntiDOS_characteristic.write("usetheforce...band".encode(), withResponse=True) + + # Enable DFU notifications: + if self.verbosity > 1: + print("[INIT] Configuring DFU descriptor") + self.DFU_descriptor.write(struct.pack('<bb', 0x01, 0x00), withResponse = True) + + # No idea what this is for. Possibly a device ID of sorts? Read request returns '00 00 09 00 0c 00 02 02': + if self.verbosity > 1: + print("[INIT] Reading DFU2 characteristic") + _ = self.DFU2_characteristic.read() + + # Enable API notifications: + if self.verbosity > 1: + print("[INIT] Configuring API dectriptor") + self.API_descriptor.write(struct.pack('<bb', 0x01, 0x00), withResponse = True) + + self.wake() + + # Finished initializing: + if self.verbosity > 1: + print("[INIT] Initialization complete\n") + + def disconnect(self): + if self.verbosity > 0: + print("[INFO] Disconnecting") + + self.p.disconnect() + + def wake(self): + ''' + Bring device out of sleep mode (can only be done if device was in sleep, not deep sleep). + If in deep sleep, the device should be connected to USB power to wake. + ''' + if self.verbosity > 2: + print("[SEND {}] Waking".format(self.sequence)) + + self._send(characteristic=self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=powerCommandIDs["wake"], + payload=[]) # empty payload + + self.getAcknowledgement("Wake") + + def sleep(self, deepSleep=False): + ''' + Put device to sleep or deep sleep (deep sleep needs USB power connected to wake up) + ''' + if deepSleep: + sleepCommID=powerCommandIDs["deepSleep"] + if self.verbosity > 0: + print("[INFO] Going into deep sleep. Connect USB power to wake.") + else: + sleepCommID=powerCommandIDs["sleep"] + self._send(characteristic=self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=sleepCommID, + payload=[]) #empty payload + + def setLEDColor(self, red = None, green = None, blue = None): + ''' + Set device LED color based on RGB vales (each can range between 0 and 0xFF) + ''' + if self.verbosity > 2: + print("[SEND {}] Setting main LED colour to [{}, {}, {}]".format(self.sequence, red, green, blue)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['userIO'], # 0x1a + commID = userIOCommandIDs["allLEDs"], # 0x0e + payload = [0x00, 0x0e, red, green, blue]) + + self.getAcknowledgement("LED/backlight") + + def setBackLEDIntensity(self, brightness=None): + ''' + Set device LED backlight intensity based on 0-255 values + + NOTE: this is not the same as aiming - it only turns on the LED + ''' + if self.verbosity > 2: + print("[SEND {}] Setting backlight intensity to {}".format(self.sequence, brightness)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['userIO'], + commID = userIOCommandIDs["allLEDs"], + payload = [0x00, 0x01, brightness]) + + self.getAcknowledgement("LED/backlight") + + def roll(self, speed=None, heading=None): + ''' + Start to move the Sphero at a given direction and speed. + heading: integer from 0 - 360 (degrees) + speed: Integer from 0 - 255 + + Note: the zero heading should be set at startup with the resetHeading method. Otherwise, it may + seem that the sphero doesn't honor the heading argument + ''' + if self.verbosity > 2: + print("[SEND {}] Rolling with speed {} and heading {}".format(self.sequence, speed, heading)) + + if abs(speed) > 255: + print("WARNING: roll speed parameter outside of allowed range (-255 to +255)") + + if speed < 0: + speed = -1*speed+256 # speed values > 256 in the send packet make the spero go in reverse + + speedH = (speed & 0xFF00) >> 8 + speedL = speed & 0xFF + headingH = (heading & 0xFF00) >> 8 + headingL = heading & 0xFF + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['driving'], + commID = drivingCommands["driveWithHeading"], + payload = [speedL, headingH, headingL, speedH]) + + self.getAcknowledgement("Roll") + + def resetHeading(self): + ''' + Reset the heading zero angle to the current heading (useful during aiming) + Note: in order to manually rotate the sphero, you need to call stabilization(False). + Once the heading has been set, call stabilization(True). + ''' + if self.verbosity > 2: + print("[SEND {}] Resetting heading".format(self.sequence)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['driving'], + commID = drivingCommands["resetHeading"], + payload = []) #empty payload + + self.getAcknowledgement("Heading") + + def returnMainApplicationVersion(self): + ''' + Sends command to return application data in a notification + ''' + if self.verbosity > 2: + print("[SEND {}] Requesting firmware version".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID = deviceID['systemInfo'], + commID = SystemInfoCommands['mainApplicationVersion'], + payload = []) # empty + + self.getAcknowledgement("Firmware") + + def getBatteryVoltage(self): + ''' + Sends command to return battery voltage data in a notification. + Data printed to console screen by the handleNotifications() method in the MyDelegate class. + ''' + if self.verbosity > 2: + print("[SEND {}] Requesting battery voltage".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=powerCommandIDs['batteryVoltage'], + payload=[]) # empty + + self.getAcknowledgement("Battery") + + def stabilization(self, stab = True): + ''' + Sends command to turn on/off the motor stabilization system (required when manually turning/aiming the sphero) + ''' + if stab == True: + if self.verbosity > 2: + print("[SEND {}] Enabling stabilization".format(self.sequence)) + val = 1 + else: + if self.verbosity > 2: + print("[SEND {}] Disabling stabilization".format(self.sequence)) + val = 0 + self._send(self.API_V2_characteristic, + devID=deviceID['driving'], + commID=drivingCommands['stabilization'], + payload=[val]) + + self.getAcknowledgement("Stabilization") + + def wait(self, delay): + ''' + This is a non-blocking delay command. It is similar to time.sleep(), except it allows asynchronous + notification handling to still be performed. + ''' + start = time.time() + while(1): + self.p.waitForNotifications(0.001) + if time.time() - start > delay: + break + + def _send(self, characteristic=None, devID=None, commID=None, payload=[]): + ''' + A generic "send" method, which will be used by other methods to send a command ID, payload and + appropriate checksum to a specified device ID. Mainly useful because payloads are optional, + and can be of varying length, to convert packets to binary, and calculate and send the + checksum. For internal use only. + + Packet structure has the following format (in order): + + - Start byte: always 0x8D + - Flags byte: indicate response required, etc + - Virtual device ID: see _constants.py + - Command ID: see _constants.py + - Sequence number: Seems to be arbitrary. I suspect it is used to match commands to response packets (in which the number is echoed). + - Payload: Could be varying number of bytes (incl. none), depending on the command + - Checksum: See below for calculation + - End byte: always 0xD8 + + ''' + sendBytes = [sendPacketConstants["StartOfPacket"], + sum([flags["resetsInactivityTimeout"], flags["requestsResponse"]]), + devID, + commID, + self.sequence] + payload # concatenate payload list + + self.sequence += 1 # Increment sequence number, ensures we can identify response packets are for this command + if self.sequence > 255: + self.sequence = 0 + + # Compute and append checksum and add EOP byte: + # From Sphero docs: "The [checksum is the] modulo 256 sum of all the bytes + # from the device ID through the end of the data payload, + # bit inverted (1's complement)" + # For the sphero mini, the flag bits must be included too: + checksum = 0 + for num in sendBytes[1:]: + checksum = (checksum + num) & 0xFF # bitwise "and to get modulo 256 sum of appropriate bytes + checksum = 0xff - checksum # bitwise 'not' to invert checksum bits + sendBytes += [checksum, sendPacketConstants["EndOfPacket"]] # concatenate + + # Convert numbers to bytes + output = b"".join([x.to_bytes(1, byteorder='big') for x in sendBytes]) + + #send to specified characteristic: + characteristic.write(output, withResponse = True) + + def getAcknowledgement(self, ack): + #wait up to 10 secs for correct acknowledgement to come in, including sequence number! + start = time.time() + while(1): + self.p.waitForNotifications(1) + if self.sphero_delegate.notification_seq == self.sequence-1: # use one less than sequence, because _send function increments it for next send. + if self.verbosity > 3: + print("[RESP {}] {}".format(self.sequence-1, self.sphero_delegate.notification_ack)) + self.sphero_delegate.clear_notification() + break + elif self.sphero_delegate.notification_seq >= 0: + print("Unexpected ACK. Expected: {}/{}, received: {}/{}".format( + ack, self.sequence, self.sphero_delegate.notification_ack.split()[0], + self.sphero_delegate.notification_seq) + ) + if time.time() > start + 10: + print("Timeout waiting for acknowledgement: {}/{}".format(ack, self.sequence)) + break + +# ======================================================================= +# The following functions are experimental: +# ======================================================================= + + def configureCollisionDetection(self, + xThreshold = 50, + yThreshold = 50, + xSpeed = 50, + ySpeed = 50, + deadTime = 50, # in 10 millisecond increments + method = 0x01, # Must be 0x01 + callback = None): + ''' + Appears to function the same as other Sphero models, however speed settings seem to have no effect. + NOTE: Setting to zero seems to cause bluetooth errors with the Sphero Mini/bluepy library - set to + 255 to make it effectively disabled. + + deadTime disables future collisions for a short period of time to avoid repeat triggering by the same + event. Set in 10ms increments. So if deadTime = 50, that means the delay will be 500ms, or half a second. + + From Sphero docs: + + xThreshold/yThreshold: An 8-bit settable threshold for the X (left/right) and Y (front/back) axes + of Sphero. + + xSpeed/ySpeed: An 8-bit settable speed value for the X and Y axes. This setting is ranged by the + speed, then added to xThreshold, yThreshold to generate the final threshold value. + ''' + + if self.verbosity > 2: + print("[SEND {}] Configuring collision detection".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['configureCollision'], + payload=[method, xThreshold, xSpeed, yThreshold, ySpeed, deadTime]) + + self.collision_detection_callback = callback + + self.getAcknowledgement("Collision") + + def configureSensorStream(self): # Use default values + ''' + Send command to configure sensor stream using default values as found during bluetooth + sniffing of the Sphero Edu app. + + Must be called after calling configureSensorMask() + ''' + bitfield1 = 0b00000000 # Unknown function - needs experimenting + bitfield2 = 0b00000000 # Unknown function - needs experimenting + bitfield3 = 0b00000000 # Unknown function - needs experimenting + bitfield4 = 0b00000000 # Unknown function - needs experimenting + + if self.verbosity > 2: + print("[SEND {}] Configuring sensor stream".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['configureSensorStream'], + payload=[bitfield1, bitfield1, bitfield1, bitfield1]) + + self.getAcknowledgement("Sensor") + + def configureSensorMask(self, + sample_rate_divisor = 0x25, # Must be > 0 + packet_count = 0, + IMU_pitch = False, + IMU_roll = False, + IMU_yaw = False, + IMU_acc_x = False, + IMU_acc_y = False, + IMU_acc_z = False, + IMU_gyro_x = False, + IMU_gyro_y = False, + IMU_gyro_z = False): + + ''' + Send command to configure sensor mask using default values as found during bluetooth + sniffing of the Sphero Edu app. From experimentation, it seems that these are he functions of each: + + Sampling_rate_divisor. Slow data EG: Set to 0x32 to the divide data rate by 50. Setting below 25 (0x19) causes + bluetooth errors + + Packet_count: Select the number of packets to transmit before ending the stream. Set to zero to stream infinitely + + All IMU bool parameters: Toggle transmission of that value on or off (e.g. set IMU_acc_x = True to include the + X-axis accelerometer readings in the sensor stream) + ''' + + # Construct bitfields based on function parameters: + IMU_bitfield1 = (IMU_pitch<<2) + (IMU_roll<<1) + IMU_yaw + IMU_bitfield2 = ((IMU_acc_y<<7) + (IMU_acc_z<<6) + (IMU_acc_x<<5) + \ + (IMU_gyro_y<<4) + (IMU_gyro_x<<2) + (IMU_gyro_z<<2)) + + if self.verbosity > 2: + print("[SEND {}] Configuring sensor mask".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensorMask'], + payload=[0x00, # Unknown param - altering it seems to slow data rate. Possibly averages multiple readings? + sample_rate_divisor, + packet_count, # Packet count: select the number of packets to stop streaming after (zero = infinite) + 0b00, # Unknown param: seems to be another accelerometer bitfield? Z-acc, Y-acc + IMU_bitfield1, + IMU_bitfield2, + 0b00]) # reserved, Position?, Position?, velocity?, velocity?, Y-gyro, timer, reserved + + self.getAcknowledgement("Mask") + + ''' + Since the sensor values arrive as unlabelled lists in the order that they appear in the bitfields above, we need + to create a list of sensors that have been configured.Once we have this list, then in the default_delegate class, + we can get sensor values as attributes of the sphero_mini class. + e.g. print(sphero.IMU_yaw) # displays the current yaw angle + ''' + + # Initialize dictionary with sensor names as keys and their bool values (set by the user) as values: + availableSensors = {"IMU_pitch" : IMU_pitch, + "IMU_roll" : IMU_roll, + "IMU_yaw" : IMU_yaw, + "IMU_acc_y" : IMU_acc_y, + "IMU_acc_z" : IMU_acc_z, + "IMU_acc_x" : IMU_acc_x, + "IMU_gyro_y" : IMU_gyro_y, + "IMU_gyro_x" : IMU_gyro_x, + "IMU_gyro_z" : IMU_gyro_z} + + # Create list of of only sensors that have been "activated" (set as true in the method arguments): + self.configured_sensors = [name for name in availableSensors if availableSensors[name] == True] + + def sensor1(self): # Use default values + ''' + Unknown function. Observed in bluetooth sniffing. + ''' + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensor1'], + payload=[0x01]) + + self.getAcknowledgement("Sensor1") + + def sensor2(self): # Use default values + ''' + Unknown function. Observed in bluetooth sniffing. + ''' + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensor2'], + payload=[0x00]) + + self.getAcknowledgement("Sensor2") + +# ======================================================================= + +class MyDelegate(btle.DefaultDelegate): + + ''' + This class handles notifications (both responses and asynchronous notifications). + + Usage of this class is described in the Bluepy documentation + + ''' + + def __init__(self, sphero_class, user_delegate): + self.sphero_class = sphero_class # for saving sensor values as attributes of sphero class instance + self.user_delegate = user_delegate # to directly notify users of callbacks + btle.DefaultDelegate.__init__(self) + self.clear_notification() + self.notificationPacket = [] + + def clear_notification(self): + self.notification_ack = "DEFAULT ACK" + self.notification_seq = -1 + + def bits_to_num(self, bits): + ''' + This helper function decodes bytes from sensor packets into single precision floats. Encoding follows the + the IEEE-754 standard. + ''' + num = int(bits, 2).to_bytes(len(bits) // 8, byteorder='little') + num = struct.unpack('f', num)[0] + return num + + def handleNotification(self, cHandle, data): + ''' + This method acts as an interrupt service routine. When a notification comes in, this + method is invoked, with the variable 'cHandle' being the handle of the characteristic that + sent the notification, and 'data' being the payload (sent one byte at a time, so the packet + needs to be reconstructed) + + The method keeps appending bytes to the payload packet byte list until end-of-packet byte is + encountered. Note that this is an issue, because 0xD8 could be sent as part of the payload of, + say, the battery voltage notification. In future, a more sophisticated method will be required. + ''' + # Allow the user to intercept and process data first.. + if self.user_delegate != None: + if self.user_delegate.handleNotification(cHandle, data): + return + + print("Received notification with packet %s", str(data)) + + for data_byte in data: # parse each byte separately (sometimes they arrive simultaneously) + + self.notificationPacket.append(data_byte) # Add new byte to packet list + + # If end of packet (need to find a better way to segment the packets): + if data_byte == sendPacketConstants['EndOfPacket']: + # Once full the packet has arrived, parse it: + # Packet structure is similar to the outgoing send packets (see docstring in sphero_mini._send()) + + # Attempt to unpack. Might fail if packet is too badly corrupted + try: + start, flags_bits, devid, commcode, seq, *notification_payload, chsum, end = self.notificationPacket + except ValueError: + print("Warning: notification packet unparseable %s", self.notificationPacket) + self.notificationPacket = [] # Discard this packet + return # exit + + # Compute and append checksum and add EOP byte: + # From Sphero docs: "The [checksum is the] modulo 256 sum of all the bytes + # from the device ID through the end of the data payload, + # bit inverted (1's complement)" + # For the sphero mini, the flag bits must be included too: + checksum_bytes = [flags_bits, devid, commcode, seq] + notification_payload + checksum = 0 # init + for num in checksum_bytes: + checksum = (checksum + num) & 0xFF # bitwise "and to get modulo 256 sum of appropriate bytes + checksum = 0xff - checksum # bitwise 'not' to invert checksum bits + if checksum != chsum: # check computed checksum against that recieved in the packet + print("Warning: notification packet checksum failed - %s", str(self.notificationPacket)) + self.notificationPacket = [] # Discard this packet + return # exit + + # Check if response packet: + if flags_bits & flags['isResponse']: # it is a response + + # Use device ID and command code to determine which command is being acknowledged: + if devid == deviceID['powerInfo'] and commcode == powerCommandIDs['wake']: + self.notification_ack = "Wake acknowledged" # Acknowledgement after wake command + + elif devid == deviceID['driving'] and commcode == drivingCommands['driveWithHeading']: + self.notification_ack = "Roll command acknowledged" + + elif devid == deviceID['driving'] and commcode == drivingCommands['stabilization']: + self.notification_ack = "Stabilization command acknowledged" + + elif devid == deviceID['userIO'] and commcode == userIOCommandIDs['allLEDs']: + self.notification_ack = "LED/backlight color command acknowledged" + + elif devid == deviceID['driving'] and commcode == drivingCommands["resetHeading"]: + self.notification_ack = "Heading reset command acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["configureCollision"]: + self.notification_ack = "Collision detection configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["configureSensorStream"]: + self.notification_ack = "Sensor stream configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensorMask"]: + self.notification_ack = "Mask configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensor1"]: + self.notification_ack = "Sensor1 acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensor2"]: + self.notification_ack = "Sensor2 acknowledged" + + elif devid == deviceID['powerInfo'] and commcode == powerCommandIDs['batteryVoltage']: + V_batt = notification_payload[2] + notification_payload[1]*256 + notification_payload[0]*65536 + V_batt /= 100 # Notification gives V_batt in 10mV increments. Divide by 100 to get to volts. + self.notification_ack = "Battery voltage:" + str(V_batt) + "v" + self.sphero_class.v_batt = V_batt + + elif devid == deviceID['systemInfo'] and commcode == SystemInfoCommands['mainApplicationVersion']: + version = '.'.join(str(x) for x in notification_payload) + self.notification_ack = "Firmware version: " + version + self.sphero_class.firmware_version = notification_payload + + else: + self.notification_ack = "Unknown acknowledgement" #print(self.notificationPacket) + print(self.notificationPacket, "===================> Unknown ack packet") + + self.notification_seq = seq + + else: # Not a response packet - therefore, asynchronous notification (e.g. collision detection, etc): + + # Collision detection: + if devid == deviceID['sensor'] and commcode == sensorCommands['collisionDetectedAsync']: + # The first four bytes are data that is still un-parsed. the remaining unsaved bytes are always zeros + _, _, _, _, _, _, axis, _, Y_mag, _, X_mag, *_ = notification_payload + if axis == 1: + dir = "Left/right" + else: + dir = 'Forward/back' + print("Collision detected:") + print("\tAxis: %s", dir) + print("\tX_mag: %s", X_mag) + print("\tY_mag: %s", Y_mag) + + if self.sphero_class.collision_detection_callback is not None: + self.notificationPacket = [] # need to clear packet, in case new notification comes in during callback + self.sphero_class.collision_detection_callback() + + # Sensor response: + elif devid == deviceID['sensor'] and commcode == sensorCommands['sensorResponse']: + # Convert to binary, pad bytes with leading zeros: + val = '' + for byte in notification_payload: + val += format(int(bin(byte)[2:], 2), '#010b')[2:] + + # Break into 32-bit chunks + nums = [] + while(len(val) > 0): + num, val = val[:32], val[32:] # Slice off first 16 bits + nums.append(num) + + # convert from raw bits to float: + nums = [self.bits_to_num(num) for num in nums] + + # Set sensor values as class attributes: + for name, value in zip(self.sphero_class.configured_sensors, nums): + print("Setting sensor %s at %s.", name, str(value)) + setattr(self.sphero_class, name, value) + + # Unrecognized packet structure: + else: + self.notification_ack = "Unknown asynchronous notification" #print(self.notificationPacket) + print(str(self.notificationPacket) + " ===================> Unknown async packet") + + self.notificationPacket = [] # Start new payload after this byte \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/dynamicPathPlanning.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/dynamicPathPlanning.py new file mode 100644 index 0000000..6607c85 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/dynamicPathPlanning.py @@ -0,0 +1,504 @@ +import numpy as np +import matplotlib.pyplot as plt +import robotController as rc +import mapGeneration as mg +import random +from controller import Robot + +#======[Scenario Control functions]============================================ + +#This function causes the robot to wait some time steps before starting it's operation +#DO NOT CHANGE THIS FUNCTION OR IT'S CALL IN THE CODE +def waitRandomTimeSteps(turtle, stop=50): + initialPose = np.zeros(7) + initialPose[0:2] = findRobotPosition(turtle) + turtle.setPoseGoal(initialPose) + turtle.drive_goalPose() + nRand = random.randint(1, stop) + for i in range(nRand): + turtle.robot.step(turtle.TIME_STEP) + turtle.update() + +#=========[Example functions for finding objects in the map]=================== + +def findTargetPosition(turtlebot): + mapInd = turtlebot.findInMap_FinalTarget() + if(len(mapInd) != 0): + pos = turtlebot.getPositionFromMapIndex(mapInd[0]) + else: + pos = [] + return np.array(pos, dtype=float).tolist() + +def findRobotPosition(turtlebot): + mapInds = turtlebot.findInMap_Robot() + robotPos = np.zeros((len(mapInds), 2)) + for i in range(len(mapInds)): + pos = turtlebot.getPositionFromMapIndex(mapInds[i]) + robotPos[i, ...] = pos + XYInd = np.average(robotPos, axis=0) + return np.array(XYInd, dtype=float).tolist() + +def findObstaclePositions(turtlebot): + mapInds = turtlebot.findInMap_Obstacles() + obsPos = np.zeros((len(mapInds), 2)) + for i in range(len(mapInds)): + pos = turtlebot.getPositionFromMapIndex(mapInds[i]) + obsPos[i, ...] = pos + return np.array(obsPos, dtype=float).tolist() + +#=========[Framework for own implementations of path tracking and planning]==== + +def checkForPossibleCollisions(turtlebot, turtlePos, collisionCntr): + #Fill this with your collision detection between the planed path and + #moving obstacles + hitBox = 0 + + faktor = 0.1 + + turtlePos1 = [turtlePos[0] + faktor, turtlePos[1] + faktor] + turtlePos2 = [turtlePos[0] + faktor, turtlePos[1] - faktor] + turtlePos3 = [turtlePos[0] - faktor, turtlePos[1] + faktor] + turtlePos4 = [turtlePos[0] - faktor, turtlePos[1] - faktor] + + turtlePos5 = [turtlePos[0] + faktor, turtlePos[1] ] + turtlePos6 = [turtlePos[0] , turtlePos[1] + faktor] + turtlePos7 = [turtlePos[0] - faktor, turtlePos[1] ] + turtlePos8 = [turtlePos[0] , turtlePos[1] - faktor] + + turtlePosBool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos)) + turtlePos1Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos1)) + turtlePos2Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos2)) + turtlePos3Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos3)) + turtlePos4Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos4)) + turtlePos5Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos5)) + turtlePos6Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos6)) + turtlePos7Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos7)) + turtlePos8Bool = bool(turtlebot.isMapAtPosition_Obstacle(turtlePos8)) + + hitBox = turtlePosBool | turtlePos1Bool | turtlePos2Bool | turtlePos3Bool | turtlePos4Bool | turtlePos5Bool | turtlePos6Bool | turtlePos7Bool | turtlePos8Bool + + if(hitBox): + collisionCntr += 1 + return True + + if(~hitBox): + return False + +def staticPathTracking(turtlebot, path, indCntr): + #This delta value determines the precision of the position control during path tracking + #A high value leads towards a larger curve radius at corners in the path, + # which might result in a smoothing of the path and keeping the velocity high. + #A lower value sets a higher precision for the precision control and thus, + # the robot might slow down when reaching a path point, but it stays very + # close to the original path + delta = 0.05 + if(indCntr == (len(path)-1)): + delta = 0.01 + #Control the turtlebot to drive towards the target position + isNearGoal = turtlebot.drive_goalPose(delta=delta) + #If the robot is close to the goal select the next goal position + if (isNearGoal): + indCntr += 1 + i = min(indCntr, len(path)) + if i == len(path): + indCntr = -1 + else: + #print("Aktuelle Position Pfad: ", path[i]) + turtlebot.setPoseGoal(path[i]) + return indCntr + +def planInitialPath(turtle): + + initialObstacles = findObstaclePositions(turtle) + initialPose = findRobotPosition(turtle) + initialFinalTarget = findTargetPosition(turtle) + + plt.scatter(np.array(initialObstacles)[..., 0], np.array(initialObstacles)[..., 1]) + plt.scatter(np.array(initialPose)[..., 0], np.array(initialPose)[..., 1] ) + plt.scatter(np.array(initialFinalTarget)[..., 0], np.array(initialFinalTarget)[..., 1]) + plt.show() + plt.pause(0.001) + + from matplotlib import pyplot as ppl + from matplotlib import cm + import random, sys, math, os.path + from matplotlib.pyplot import imread + + #Implementation of RRT + + MAP_IMG = './karte.png' #Black and white image for a map + MIN_NUM_VERT = 20 # Minimum number of vertex in the graph + MAX_NUM_VERT = 1500 # Maximum number of vertex in the graph + STEP_DISTANCE = 20 # Maximum distance between two vertex + SEED = None # For random numbers + + def rapidlyExploringRandomTree(ax, img, start, goal, seed=None): + hundreds = 100 + seed = random.seed(seed) + #print("Zufallsseed: ", seed) + points = [] + graph = [] + points.append(start) + graph.append((start, [])) + print('Generating and conecting random points') + occupied = True + phaseTwo = False + + # Phase two values (points 5 step distances around the goal point) + minX = max(goal[0] - 5 * STEP_DISTANCE, 0) + maxX = min(goal[0] + 5 * STEP_DISTANCE, len(img[0]) - 1) + minY = max(goal[1] - 5 * STEP_DISTANCE, 0) + maxY = min(goal[1] + 5 * STEP_DISTANCE, len(img) - 1) + + i = 0 + while (goal not in points) and (len(points) < MAX_NUM_VERT): + if (i % 100) == 0: + print(i, 'points randomly generated') + + if (len(points) % hundreds) == 0: + print(len(points), 'vertex generated') + hundreds = hundreds + 100 + + while (occupied): + if phaseTwo and (random.random() > 0.8): + point = [random.randint(minX, maxX), random.randint(minY, maxY)] + else: + point = [random.randint(0, len(img[0]) - 1), random.randint(0, len(img) - 1)] + + if (img[point[1]][point[0]][0] * 255 == 255): + occupied = False + + occupied = True + + nearest = findNearestPoint(points, point) + newPoints = connectPoints(point, nearest, img) + addToGraph(ax, graph, newPoints, point) + newPoints.pop(0) # The first element is already in the points list + points.extend(newPoints) + ppl.draw() + i = i + 1 + + if len(points) >= MIN_NUM_VERT: + if not phaseTwo: + print('Phase Two') + phaseTwo = True + + if phaseTwo: + nearest = findNearestPoint(points, goal) + newPoints = connectPoints(goal, nearest, img) + addToGraph(ax, graph, newPoints, goal) + newPoints.pop(0) + points.extend(newPoints) + ppl.draw() + + if goal in points: + print('Goal found, total vertex in graph:', len(points), 'total random points generated:', i) + + path = searchPath(graph, start, [start]) + + for i in range(len(path) - 1): + ax.plot([path[i][0], path[i + 1][0]], [path[i][1], path[i + 1][1]], color='g', linestyle='-', + linewidth=2) + ppl.draw() + + print('Showing resulting map') + print('Final path:', path) + print('The final path is made from:', len(path), 'connected points') + else: + path = None + print('Reached maximum number of vertex and goal was not found') + print('Total vertex in graph:', len(points), 'total random points generated:', i) + print('Showing resulting map') + + ppl.show() + return path + + def searchPath(graph, point, path): + for i in graph: + if point == i[0]: + p = i + + if p[0] == graph[-1][0]: + return path + + for link in p[1]: + path.append(link) + finalPath = searchPath(graph, link, path) + + if finalPath != None: + return finalPath + else: + path.pop() + + def addToGraph(ax, graph, newPoints, point): + if len(newPoints) > 1: # If there is anything to add to the graph + for p in range(len(newPoints) - 1): + nearest = [nearest for nearest in graph if (nearest[0] == [newPoints[p][0], newPoints[p][1]])] + nearest[0][1].append(newPoints[p + 1]) + graph.append((newPoints[p + 1], [])) + + if not p == 0: + ax.plot(newPoints[p][0], newPoints[p][1], '+k') # First point is already painted + ax.plot([newPoints[p][0], newPoints[p + 1][0]], [newPoints[p][1], newPoints[p + 1][1]], color='k', + linestyle='-', linewidth=1) + + if point in newPoints: + ax.plot(point[0], point[1], '.g') # Last point is green + else: + ax.plot(newPoints[p + 1][0], newPoints[p + 1][1], '+k') # Last point is not green + + def connectPoints(a, b, img): + newPoints = [] + newPoints.append([b[0], b[1]]) + step = [(a[0] - b[0]) / float(STEP_DISTANCE), (a[1] - b[1]) / float(STEP_DISTANCE)] + + # Set small steps to check for walls + pointsNeeded = int(math.floor(max(math.fabs(step[0]), math.fabs(step[1])))) + + if math.fabs(step[0]) > math.fabs(step[1]): + if step[0] >= 0: + step = [1, step[1] / math.fabs(step[0])] + else: + step = [-1, step[1] / math.fabs(step[0])] + + else: + if step[1] >= 0: + step = [step[0] / math.fabs(step[1]), 1] + else: + step = [step[0] / math.fabs(step[1]), -1] + + blocked = False + for i in range(pointsNeeded + 1): # Creates points between graph and solitary point + for j in range(STEP_DISTANCE): # Check if there are walls between points + coordX = round(newPoints[i][0] + step[0] * j) + coordY = round(newPoints[i][1] + step[1] * j) + + if coordX == a[0] and coordY == a[1]: + break + if coordY >= len(img) or coordX >= len(img[0]): + break + if img[int(coordY)][int(coordX)][0] * 255 < 255: + blocked = True + if blocked: + break + + if blocked: + break + if not (coordX == a[0] and coordY == a[1]): + newPoints.append( + [newPoints[i][0] + (step[0] * STEP_DISTANCE), newPoints[i][1] + (step[1] * STEP_DISTANCE)]) + + if not blocked: + newPoints.append([a[0], a[1]]) + return newPoints + + def findNearestPoint(points, point): + best = (sys.maxsize, sys.maxsize, sys.maxsize) + for p in points: + if p == point: + continue + dist = math.sqrt((p[0] - point[0]) ** 2 + (p[1] - point[1]) ** 2) + if dist < best[2]: + best = (p[0], p[1], dist) + return (best[0], best[1]) + + karte = np.ones([80, 80, 3], dtype=int) + + # Koordinatentransformation + initialObstacles = [[int(x[0] * 10), int(x[1] * 10)] for x in initialObstacles] + initialPose = [initialPose[0] * 10, initialPose[1] * 10] + initialFinalTarget = [initialFinalTarget[0] * 10, initialFinalTarget[1] * 10] + + initialObstacles = [[int(x[0] + 40), int(x[1] - 40)] for x in initialObstacles] + initialPose = [initialPose[0] + 40, initialPose[1] - 40] + initialFinalTarget = [initialFinalTarget[0] + 40, initialFinalTarget[1] - 40] + + initialObstacles = [[int(x[0]), int(x[1] * (-1))] for x in initialObstacles] + initialPose = [int(initialPose[0]), int(initialPose[1] * -1)] + initialFinalTarget = [int(initialFinalTarget[0]), int(initialFinalTarget[1] * -1)] + + # Karte Hindernisse einzeichnen + rot = np.array(0, dtype=int) + gruen = np.array(0, dtype=int) + blau = np.array(0, dtype=int) + + for k in initialObstacles: + karte[k[1], k[0], 0] = rot; + karte[k[1], k[0], 1] = gruen; + karte[k[1], k[0], 2] = blau; + + karte[k[1] + 1, k[0] + 0, 0] = rot; + karte[k[1] + 1, k[0] + 0, 1] = gruen; + karte[k[1] + 1, k[0] + 0, 2] = blau; + + karte[k[1] - 0, k[0] + 1, 0] = rot; + karte[k[1] - 0, k[0] + 1, 1] = gruen; + karte[k[1] - 0, k[0] + 1, 2] = blau; + + karte[k[1] + 0, k[0] - 1, 0] = rot; + karte[k[1] + 0, k[0] - 1, 1] = gruen; + karte[k[1] + 0, k[0] - 1, 2] = blau; + + karte[k[1] - 1, k[0] - 0, 0] = rot; + karte[k[1] - 1, k[0] - 0, 1] = gruen; + karte[k[1] - 1, k[0] - 0, 2] = blau; + + #Plotten als PNG speichern + from PIL import Image + im = Image.fromarray((karte * 255).astype(np.uint8)) + im.save('karte.png') + + print('Loading map... with file \'', MAP_IMG, '\'') + img = imread(MAP_IMG) + fig = ppl.gcf() + fig.clf() + ax = fig.add_subplot(1, 1, 1) + ax.imshow(img, cmap=cm.Greys_r) + ax.axis('image') + ppl.draw() + print('Map is', len(img[0]), 'x', len(img)) + start = initialPose + goal = initialFinalTarget + + path = rapidlyExploringRandomTree(ax, img, start, goal, seed=SEED) + + # Ruecktransformation + path1 = [[x[0], x[1] * (-1)] for x in path] + path2 = [[(x[0] - 40), (x[1] + 40)] for x in path1] + path3 = [[(x[0] / 10), (x[1] / 10)] for x in path2] + + path3 = [[(x[0]), x[1], 0, 0, 0, 0, 0] for x in path3] + + print("Umgerechneter Pfad", path3) + + if len(sys.argv) > 2: + print('Only one argument is needed') + elif len(sys.argv) > 1: + if os.path.isfile(sys.argv[1]): + MAP_IMG = sys.argv[1] + else: + print(sys.argv[1], 'is not a file') + + pathIndCntr = 0 + + return path3, pathIndCntr + +#=============[Evaluation functions]=========================================== +collisionCntr = 0 +recalcCntr = 0 +def updateEval_RobotCollisions(detector): + global collisionCntr + collision = np.nan_to_num(detector.getValue(), nan=0) + collisionCntr += int(collision) + return bool(collision) + +#======================================[MAIN Execution]======================== +if __name__ == "__main__": + plotting = False + nPlot = 80 + festgefahren = 0 + #Only increase this value if the simulation is running slowly + TIME_STEP = 64 + + #%%=============[DO NOT CHANGE THIS PART OF THE SCRIPT]================== + loopCntr = 0 # + turtle = rc.turtlebotController(TIME_STEP, + poseGoal_tcs=np.array([-0.863, -2.0, 0.0, 0.0, 0.0, 1.0, 3.14159])) # + detector = turtle.robot.getDevice("collisionDetector") # + detector.enable(TIME_STEP) # + # + #The map will be initialized after the first timestep (Transmission) # + turtle.robot.step(turtle.TIME_STEP) # + turtle.update() # + + #The map will have the dynamic objects after the second timestep # + turtle.robot.step(turtle.TIME_STEP) # + turtle.update() # + ScenarioFinalTarget = findTargetPosition(turtle) # + # + if plotting: + mg.plotting_GM_plotly(turtle.mapGenerator) + #DO NOT CHANGE OR REMOVE THIS FUNCTION CALL AND POSITION # + waitRandomTimeSteps(turtle) # + #===============[From here you can change the script]==================== + #%% + #here you should insert a function, which plans a path; the functions has to return a path and related index + try: + path, pathIndCntr = planInitialPath(turtle) + except TypeError: + print("Erste Pfadplanung nicht erfolgreich, neustart...") + try: + path, pathIndCntr = planInitialPath(turtle) + except TypeError: + print("Zweite Pfadplanung nicht erfolgreich, neustart...") + try: + path, pathIndCntr = planInitialPath(turtle) + except TypeError: + print("Dritte Pfadplanung nicht erfolgreich, neustart...") + exit() + + turtle.setPoseGoal(path[pathIndCntr]) + startTime = turtle.robot.getTime() + + inhibitCntr = 0 + while turtle.robot.step(turtle.TIME_STEP) != -1: + #%%===[DO NOT CHANGE THIS PART OF THE LOOP]========================== + loopCntr += 1 # + turtle.update() + + collision = checkForPossibleCollisions(turtle, findRobotPosition(turtle), collisionCntr) + + if ( (plotting) & (loopCntr % nPlot == 0)): # + mg.plotting_GM_plotly(turtle.mapGenerator) # + #====[From here you can change the loop]============================= + #%% + #Follow the static path towards the index pathIndCntr in the path + inhibitCntr = max(0, inhibitCntr - 1) + + if(collision == False): + pathIndCntr = staticPathTracking(turtle, path, pathIndCntr) + + if(collision == True): + print("Knallt") + turtle.stop() + festgefahren += 1 + + if(festgefahren == 10): + #Berechnen neue Pos + vorherigePos = path[pathIndCntr-1] + neueRichtung = [vorherigePos[0] / 2, vorherigePos[1] / 2, 0, 0, 0, 0, 0] + path.insert(pathIndCntr, neueRichtung) + + #Fahre neue Pos an bis du da bist + #Wenn du da bist festgefahren 0 und hoffentlich keine Collsion mehr + recalcCntr += 1 + inhibitCntr = 15 + festgefahren = 0 + collision = False + + #This is the exit condition. Changing this is not recommended + if(pathIndCntr == -1): + break + #End while + #%% + print("Simulation ended") + print("Timesteps with collisions: ", collisionCntr) + print("Recalculations done: ", recalcCntr) + print("Simulationtime: ", turtle.robot.getTime() - startTime) + print("") + print("Recalculation") + print("Timesteps with collisions: ", collisionCntr) + print("Recalculations done: ", recalcCntr) + print("Simulationtime: ", turtle.robot.getTime() - startTime) + + lastStateObstacles = np.array(findObstaclePositions(turtle)) + lastStateRobotPose = np.array(findRobotPosition(turtle)) + + trackRobot = np.array(turtle.trackRobot) + plt.scatter(lastStateObstacles[..., 0], lastStateObstacles[..., 1]) + plt.scatter(lastStateRobotPose[..., 0], lastStateRobotPose[..., 1]) + + plt.plot(trackRobot[..., 0], trackRobot[..., 1]) + plt.scatter(turtle.poseGoal[0], turtle.poseGoal[1]) + plt.scatter(turtle.mapGenerator.finalTarget[0], turtle.mapGenerator.finalTarget[1]) + plt.show() diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/map.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/map.py new file mode 100644 index 0000000..379645d --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/map.py @@ -0,0 +1,24 @@ +#Map erstellen + +# Standard imports +import cv2 +import numpy as np; + +# Img Objekt +cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + +img = cap.read() +#height, width = img.shape[:2] + +success, img = cap.read() +gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) +mask = cv2.inRange(gray, np.array([90]), np.array([255])) + +mask[mask == 255] = 87 + +mask = np.array(mask, dtype=np.uint8).view('S2').squeeze() + +mask[mask == b'WW'] = 'W' + +print(mask) + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/maperstellen.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/maperstellen.py new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node copy.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node copy.py new file mode 100644 index 0000000..29f2473 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node copy.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime + + +class MyNode(Node): + + def __init__(self): + super().__init__("sphero_mini") + self.publisher_ = self.create_publisher(String,"Imu",5) + #self.get_logger().info("Hello from ROS2") + + def connect(self): + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + #print(i) + + #self.publisher_.publish(i) + + + +def main(args = None): + + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(2) + + try: + while rclpy.ok(): + + sphero.configureSensorMask( + IMU_yaw=True, + IMU_pitch=True, + IMU_roll=True, + IMU_acc_y=True, + IMU_acc_z=True, + IMU_acc_x=True, + IMU_gyro_x=True, + IMU_gyro_y=True, + #IMU_gyro_z=True + ) + sphero.configureSensorStream() + + sensors_values = node.get_sensors_data(sphero) + #rclpy.logdebug(sensors_values) + node.publish_imu(sensors_values, node) + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn LEDs green + + # Aiming: + sphero.setLEDColor(red = 0, green = 0, blue = 0) # Turn main LED off + sphero.stabilization(False) # Turn off stabilization + sphero.setBackLEDIntensity(255) # turn back LED on + sphero.wait(3) # Non-blocking pau`se + sphero.resetHeading() # Reset heading + sphero.stabilization(True) # Turn on stabilization + sphero.setBackLEDIntensity(0) # Turn back LED off + + # Move around: + sphero.setLEDColor(red = 0, green = 0, blue = 255) # Turn main LED blue + sphero.roll(100, 0) # roll forwards (heading = 0) at speed = 50 + + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop + + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn main LED green + sphero.roll(-100, 0) # Keep facing forwards but roll backwards at speed = 50 + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop + + rate.sleep() + + except KeyboardInterrupt: + pass + + rclpy.shutdown() + thread.join() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node.py new file mode 100644 index 0000000..8906b23 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node.py @@ -0,0 +1,766 @@ +#!/usr/bin/env python3 +############################################################################ +#%% Imports +import rclpy +from rclpy.node import Node +from sensor_msgs.msg import Imu +import threading +from geometry_msgs.msg import Quaternion, Vector3 +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import cv2 +import time +import queue +import matplotlib.pyplot as plt +############################################################################ +#%% Blob Detector / Globale Variablen +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +params.filterByArea = True +params.minArea = 10 +params.maxArea = 200 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +#params.filterByInertia = True +#params.minInertiaRatio = 0.01 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) +############################################################################ +#%% Class Video Capture +# Bufferless VideoCapture +# Quelle ? +class VideoCapture: + def __init__(self, name): + self.cap = cv2.VideoCapture(name) + self.q = queue.Queue() + t = threading.Thread(target=self._reader) + t.daemon = True + t.start() + # read frames as soon as they are available, keeping only most recent one + def _reader(self): + while True: + ret, frame = self.cap.read() + #cv2.imshow("Cap", frame) + + if not ret: + break + if not self.q.empty(): + try: + self.q.get_nowait() # discard previous (unprocessed) frame + except queue.Empty: + pass + self.q.put(frame) + + def read(self): + return self.q.get() +############################################################################ +#%% Class Sphero Node +class MyNode(Node): + def __init__(self): + super().__init__("sphero_mini") + + def connect(self): + #Automatisch MAC-Adresse laden + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + def get_pos(self, cap, height): + #zeitanfang = time.time() + + success = False + + while(success == False): + try: + img = cap.read() + + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + keypoints = detector.detect(gray) + + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + success = True + + xTrans, yTrans = self.calc_trans(x,y, height) + except Exception: + continue + + #print("Blob X: %f Blob Y: %f" %(x,y)) + #zeitende = time.time() + #print("Dauer Programmausführung:",(zeitende-zeitanfang)) + #print("Get_Pos: X,Y:", xTrans,yTrans) + + return xTrans,yTrans + + def calc_offset(self, sphero, cap, height): + sphero.roll(100,0) + sphero.wait(1) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = self.get_pos(cap, height) + print("calc_offset: Ref Punkt x,y", ref) + + sphero.wait(1) + sphero.roll(100,180) + sphero.wait(1) + sphero.roll(0,0) + + startpunkt = self.get_pos(cap, height) + print("calc_offset: Startpunkt x,y", startpunkt) + + ref = np.array(ref) + startpunkt = np.array(startpunkt) + + start_ref = ref-startpunkt + + phi = np.arctan2(start_ref[1],start_ref[0]) + phi = np.degrees(phi) + phi = int(-phi) + + print("Phi ohne alles:", phi) + + phi = self.phi_in_range(phi) + + print("Calc_Offset: ", phi) + + return phi + + def calc_av(self,istPos, sollPos, cap, height): + startPos = istPos + + startPos = np.array(startPos) + sollPos = np.array(sollPos) + + pktSpheroKord = sollPos - startPos + + phi = np.arctan2(pktSpheroKord[1], pktSpheroKord[0]) + phi = np.degrees(phi) + + phiZiel = int(phi) + + phiZiel = self.phi_in_range(phiZiel) + + v = 100 + + #print("Calc_AV: a,v", phiZiel, v) + + return phiZiel, v + + def drive_to(self,sphero, targetPos, aOffset, cap, height): + dmin = 20 + pos = self.get_pos(cap, height) + pos = np.array(pos) + + diff = targetPos - pos + + d = np.linalg.norm(diff, ord = 2) + + ar = [] + ar.append(0) + + i = 1 + + while d > dmin: + a,v = self.calc_av(pos,targetPos, cap, height) + + aR = -aOffset - a #Fallunterscheidung? + ar.append((self.phi_in_range(aR))) + + #Fahrbefehl + if(ar[i] != ar[i-1]): + sphero.roll(v, ar[i]) + sphero.wait(0.05) + else: + sphero.wait(0.05) + + #Aktuelle Pos + pos = self.get_pos(cap, height) + pos = np.array(pos) + + #Abweichung + diff = targetPos - pos + diff = np.array(diff) + d = np.linalg.norm(diff, ord = 2) + + i = i + 1 + + sphero.roll(0,0) + print("Ziel Erreicht") + + return + + def calc_trans(self,x,y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans + + def phi_in_range(self,phi): + while(phi < 0): + phi = phi + 360 + while(phi > 360): + phi = phi - 360 + return phi +############################################################################ +#%% Class Wavefrontplanner +class waveFrontPlanner: + ############################################################################ + # WAVEFRONT ALGORITHM + # Adapted to Python Code By Darin Velarde + # Fri Jan 29 13:56:53 PST 2010 + # from C code from John Palmisano + # (www.societyofrobots.com) + ############################################################################ + + def __init__(self, mapOfWorld, slow=False): + self.__slow = slow + self.__mapOfWorld = mapOfWorld + if str(type(mapOfWorld)).find("numpy") != -1: + #If its a numpy array + self.__height, self.__width = self.__mapOfWorld.shape + else: + self.__height, self.__width = len(self.__mapOfWorld), len(self.__mapOfWorld[0]) + + self.__nothing = 000 + self.__wall = 999 + self.__goal = 1 + self.__path = "PATH" + + self.__finalPath = [] + + #Robot value + self.__robot = 254 + #Robot default Location + self.__robot_x = 0 + self.__robot_y = 0 + + #default goal location + self.__goal_x = 8 + self.__goal_y = 9 + + #temp variables + self.__temp_A = 0 + self.__temp_B = 0 + self.__counter = 0 + self.__steps = 0 #determine how processor intensive the algorithm was + + #when searching for a node with a lower value + self.__minimum_node = 250 + self.__min_node_location = 250 + self.__new_state = 1 + self.__old_state = 1 + self.__reset_min = 250 #above this number is a special (wall or robot) + ########################################################################### + + def setRobotPosition(self, x, y): + """ + Sets the robot's current position + + """ + + self.__robot_x = x + self.__robot_y = y + ########################################################################### + + def setGoalPosition(self, x, y): + """ + Sets the goal position. + + """ + + self.__goal_x = x + self.__goal_y = y + ########################################################################### + + def robotPosition(self): + return (self.__robot_x, self.__robot_y) + ########################################################################### + + def goalPosition(self): + return (self.__goal_x, self.__goal_y) + ########################################################################### + + def run(self, prnt=False): + """ + The entry point for the robot algorithm to use wavefront propagation. + + """ + + path = [] + while self.__mapOfWorld[self.__robot_x][self.__robot_y] != self.__goal: + if self.__steps > 10000: + print ("Cannot find a path.") + return + #find new location to go to + self.__new_state = self.propagateWavefront() + #update location of robot + if self.__new_state == 1: + self.__robot_x -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" % (self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 2: + self.__robot_y += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 3: + self.__robot_x += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 4: + self.__robot_y -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + self.__old_state = self.__new_state + msg = "Found the goal in %i steps:\n" % self.__steps + msg += "mapOfWorld size= %i %i\n\n" % (self.__height, self.__width) + if prnt: + print(msg) + self.printMap() + return path + ########################################################################### + + def propagateWavefront(self, prnt=False): + self.unpropagate() + #Old robot location was deleted, store new robot location in mapOfWorld + self.__mapOfWorld[self.__robot_x][self.__robot_y] = self.__robot + self.__path = self.__robot + #start location to begin scan at goal location + self.__mapOfWorld[self.__goal_x][self.__goal_y] = self.__goal + counter = 0 + while counter < 200: #allows for recycling until robot is found + x = 0 + y = 0 + #time.sleep(0.00001) + #while the mapOfWorld hasnt been fully scanned + while x < self.__height and y < self.__width: + #if this location is a wall or the goal, just ignore it + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal: + #a full trail to the robot has been located, finished! + minLoc = self.minSurroundingNodeValue(x, y) + if minLoc < self.__reset_min and \ + self.__mapOfWorld[x][y] == self.__robot: + if prnt: + print("Finished Wavefront:\n") + self.printMap() + # Tell the robot to move after this return. + return self.__min_node_location + #record a value in to this node + elif self.__minimum_node != self.__reset_min: + #if this isnt here, 'nothing' will go in the location + self.__mapOfWorld[x][y] = self.__minimum_node + 1 + #go to next node and/or row + y += 1 + if y == self.__width and x != self.__height: + x += 1 + y = 0 + #print self.__robot_x, self.__robot_y + if prnt: + print("Sweep #: %i\n" % (counter + 1)) + self.printMap() + self.__steps += 1 + counter += 1 + return 0 + ########################################################################### + + def unpropagate(self): + """ + clears old path to determine new path + stay within boundary + + """ + + for x in range(0, self.__height): + for y in range(0, self.__width): + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal and \ + self.__mapOfWorld[x][y] != self.__path: + #if this location is a wall or goal, just ignore it + self.__mapOfWorld[x][y] = self.__nothing #clear that space + ########################################################################### + + def minSurroundingNodeValue(self, x, y): + """ + this method looks at a node and returns the lowest value around that + node. + + """ + + #reset minimum + self.__minimum_node = self.__reset_min + #down + if x < self.__height -1: + if self.__mapOfWorld[x + 1][y] < self.__minimum_node and \ + self.__mapOfWorld[x + 1][y] != self.__nothing: + #find the lowest number node, and exclude empty nodes (0's) + self.__minimum_node = self.__mapOfWorld[x + 1][y] + self.__min_node_location = 3 + #up + if x > 0: + if self.__mapOfWorld[x-1][y] < self.__minimum_node and \ + self.__mapOfWorld[x-1][y] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x-1][y] + self.__min_node_location = 1 + #right + if y < self.__width -1: + if self.__mapOfWorld[x][y + 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y + 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y + 1] + self.__min_node_location = 2 + #left + if y > 0: + if self.__mapOfWorld[x][y - 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y - 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y-1] + self.__min_node_location = 4 + return self.__minimum_node + ########################################################################### + + def printMap(self): + """ + Prints out the map of this instance of the class. + + """ + + msg = '' + for temp_B in range(0, self.__height): + for temp_A in range(0, self.__width): + if self.__mapOfWorld[temp_B][temp_A] == self.__wall: + msg += "%04s" % "[#]" + elif self.__mapOfWorld[temp_B][temp_A] == self.__robot: + msg += "%04s" % "-" + elif self.__mapOfWorld[temp_B][temp_A] == self.__goal: + msg += "%04s" % "G" + else: + msg += "%04s" % str(self.__mapOfWorld[temp_B][temp_A]) + msg += "\n\n" + msg += "\n\n" + print(msg) + # + if self.__slow == True: + time.sleep(0.05) + + def plotMap(self,img,path): + #Programm Koordinaten + print("Plotten") + + imgTrans = cv2.transpose(img) # X und Y-Achse im Bild tauschen + imgPlot, ax1 = plt.subplots() + + ax1.set_title('Programm Koordinaten') + ax1.imshow(imgTrans) + ax1.set_xlabel('[px]') + ax1.set_ylabel('[px]') + ax1.scatter(path[:,0], path[:,1], color='r') + + #Bild Koordinaten + imgPlot2, ax2 = plt.subplots() + ax2.set_title('Bild Koordinaten') + ax2.set_xlabel('[px]') + ax2.set_ylabel('[px]') + ax2.imshow(img) + ax2.scatter(path[:,1], path[:,0], color='r') + + plt.show() + + return + + def getPathWelt(self,path,height,Mapobj): + + pathWeltX, pathWeltY = Mapobj.calc_trans_welt(path[:,1], path[:,0], height) + pathEckenX = [] + pathEckenY = [] + + for i,px in enumerate(pathWeltX): + if (i < len(pathWeltX)-1) & (i > 0): + if px != pathWeltX[i+1]: + if pathWeltY[i]!=pathWeltY[i-1]: + pathEckenX.append(px) + pathEckenY.append(pathWeltY[i]) + if pathWeltY[i] != pathWeltY[i+1]: + if pathWeltX[i]!=pathWeltX[i-1]: + pathEckenX.append(px) + pathEckenY.append(pathWeltY[i]) + + pathEckenX.append(pathWeltX[-1]) + pathEckenY.append(pathWeltY[-1]) + + + uebergabePath = [] + for i in range(0,len(pathEckenX)): + uebergabePath.append((pathEckenX[i],pathEckenY[i])) + + print("Ecken: ", uebergabePath) + + return uebergabePath + +############################################################################ +#%% Class Map +class mapCreate: + ############################################################################ + + def create_map(self, scale, cap): + + # Map erstellen + + # Img Objekt + #cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + img = cap.read() + + #Karte von Bild + #img = cv2.imread("D:/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/map/map.jpg") + print('Original Dimensions : ',img.shape) + + scale_percent = 100-scale # percent of original size + width = int(img.shape[1] * scale_percent / 100) + height = int(img.shape[0] * scale_percent / 100) + dim = (width, height) + + # resize image + resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA) + print('Resized Dimensions : ',resized.shape) + + gray = cv2.cvtColor(resized, cv2.COLOR_RGB2GRAY) + mask = cv2.inRange(gray, np.array([50]), np.array([255])) + + plt.plot(mask) + + mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + mask_list= np.ndarray.tolist(mask) + + #Markiere alle leeren Zellen mit 000 und alle Zellen mit Hinderniss mit 999 + for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['999' if j > 200 else '000' for j in mask_list[i]] + + mapOfWorld = np.array(mapOfWorld, dtype = int) + mapOfWorldSized = mapOfWorld.copy() + + e = 1 + #Hindernisse Größer machen + for i in range(0,mapOfWorld.shape[0]): + for j in range(0,mapOfWorld.shape[1]): + for r in range(0,e): + try: + if (mapOfWorldSized[i][j] == 999): + mapOfWorld[i][j+e] = 999 + mapOfWorld[i+e][j] = 999 + mapOfWorld[i-e][j] = 999 + mapOfWorld[i][j-e] = 999 + mapOfWorld[i+e][j+e] = 999 + mapOfWorld[i+e][j-e] = 999 + mapOfWorld[i-e][j+e] = 999 + mapOfWorld[i-e][j-e] = 999 + except Exception: + continue + + return mapOfWorld,img + ############################################################################ + + def scale_koord(self, sx, sy, gx, gy,scale): + scale_percent = 100-scale # percent of original size + + sx = int(sx*scale_percent/100) + sy = int(sy*scale_percent/100) + gx = int(gx*scale_percent/100) + gy = int(gy*scale_percent/100) + + return sx,sy,gx,gy + ############################################################################ + + def rescale_koord(self, path,scale): + scale_percent = 100-scale # percent of original size100 + + path = np.array(path) + + for i in range(len(path)): + path[i] = path[i]*100/scale_percent + + return path + ############################################################################ + + def calc_trans_welt(self, x, y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans +############################################################################### +#%% Main Funktion +def main(args = None): + #Verbindung herstellen, Node erstellen + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + #rate = node.create_rate(5) + + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + #cap = VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + cap = VideoCapture(4) + Map = mapCreate() + + img = cap.read() + height, width = img.shape[:2] + + scale = 90 + + sphero.setLEDColor(255,0,0) + sphero.wait(1) + sphero.setBackLEDIntensity(0) + sphero.stabilization(True) + + aOffset = node.calc_offset(sphero, cap, height) + + sphero.wait(1) + + #while(True): + + mapWorld,img = Map.create_map(scale, cap) + height, width = img.shape[:2] + + #Befehle für WaveFrontPlanner/Maperstellung + sy,sx = node.get_pos(cap,height) #Aktuelle Roboter Pos in Welt + sy, sx = node.calc_trans(sy,sx,height) + + gy = int(input("Bitte geben Sie die X-Zielkoordinate im Bild Koordinatensystem ein:")) #X-Koordinate im Weltkoordinaten System + gx = int(input("Bitte geben Sie die Y-Zielkoordinate im Bild Koordinatensystem ein:")) #Y-Koordinate im Weltkordinaten System + + sx,sy,gx,gy = Map.scale_koord(sx,sy,gx,gy,scale) #Kordinaten Tauschen X,Y + + start = time.time() + planner = waveFrontPlanner(mapWorld) + planner.setGoalPosition(gx,gy) + planner.setRobotPosition(sx,sy) + path = planner.run(False) + end = time.time() + print("Took %f seconds to run wavefront simulation" % (end - start)) + + path = Map.rescale_koord(path, scale) + + planner.plotMap(img,path) + + pathWelt = planner.getPathWelt(path,height,Map) + + for p in pathWelt: + node.drive_to(sphero,p,aOffset,cap, height) + sphero.wait(1) + + print("Geschafft") + + #if cv2.waitKey(10) & 0xFF == ord('q'): + # break + + rclpy.shutdown() +############################################################################ +#%% Main +if __name__ == '__main__': + main() +############################################################################ diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_alt.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_alt.py new file mode 100644 index 0000000..3b4ea79 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_alt.py @@ -0,0 +1,323 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime +import cv2 +import time +import queue, threading, time + +# Set up the detector with default parameters. +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +params.filterByArea = True +params.minArea = 10 +params.maxArea = 200 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +#params.filterByInertia = True +#params.minInertiaRatio = 0.01 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) + +# bufferless VideoCapture +class VideoCapture: + def __init__(self, name): + self.cap = cv2.VideoCapture(name) + self.q = queue.Queue() + t = threading.Thread(target=self._reader) + t.daemon = True + t.start() + # read frames as soon as they are available, keeping only most recent one + def _reader(self): + while True: + ret, frame = self.cap.read() + if not ret: + break + if not self.q.empty(): + try: + self.q.get_nowait() # discard previous (unprocessed) frame + except queue.Empty: + pass + self.q.put(frame) + + def read(self): + return self.q.get() + +class MyNode(Node): + + def __init__(self): + super().__init__("sphero_mini") + + def connect(self): + #Automatisch MAC-Adresse laden + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + def get_pos(self, cap, height): + zeitanfang = time.time() + + img = cap.read() + + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + keypoints = detector.detect(gray) + + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + + #print("Blob X: %f Blob Y: %f" %(x,y)) + + zeitende = time.time() + #print("Dauer Programmausführung:",(zeitende-zeitanfang)) + + xTrans, yTrans = self.calc_trans(x,y, height) + + #print("Get_Pos: X,Y:", xTrans,yTrans) + + return xTrans,yTrans + + def calc_offset(self, sphero, cap, height): + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = self.get_pos(cap, height) + print("calc_offset: Ref Punkt x,y", ref) + + sphero.wait(1) + sphero.roll(100,180) + sphero.wait(2) + sphero.roll(0,0) + + startpunkt = self.get_pos(cap, height) + print("calc_offset: Startpunkt x,y", startpunkt) + + ref = np.array(ref) + startpunkt = np.array(startpunkt) + + start_ref = ref-startpunkt + + phi = np.arctan2(start_ref[1],start_ref[0]) + phi = np.degrees(phi) + phi = int(phi) + + print("Phi ohne alles:", phi) + + if(phi < 0): + phi = phi + 360 + + if(phi > 360): + phi = phi - 360 + + print("Calc_Offset: ", phi) + + return phi + + def calc_av(self, sollPos, cap, height): + + startPos = self.get_pos(cap, height) + + startPos = np.array(startPos) + sollPos = np.array(sollPos) + + pktSpheroKord = sollPos - startPos + + phi = np.arctan2(pktSpheroKord[1], pktSpheroKord[0]) + phi = np.degrees(phi) + + phiZiel = int(phi) + + if(phiZiel < 0): + phiZiel = phiZiel + 360 + + if(phi > 360): + phi = phi - 360 + + v = 100 + + print("Calc_AV: a,v", phiZiel, v) + + return phiZiel, v + + def drive_to(self,sphero, targetPos, aOffset, cap, height): + dmin = 50 + pos = self.get_pos(cap, height) + pos = np.array(pos) + + diff = targetPos - pos + + d = np.linalg.norm(diff, ord = 2) + + while d > dmin: + a,v = self.calc_av(targetPos, cap, height) + ar = aOffset + a #Fallunterscheidung? + + pos = self.get_pos(cap, height) + pos = np.array(pos) + diff = targetPos - pos + diff = np.array(diff) + d = np.linalg.norm(diff, ord = 2) + + sphero.roll(v, ar) + time.sleep(0.1) + + sphero.roll(0,ar) + + return + + def calc_trans(self,x,y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans + +def main(args = None): + #Verbindung herstellen, Node erstellen + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(5) + + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + cap = VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + + img = cap.read() + height, width = img.shape[:2] + + sphero.setLEDColor(255,0,0) + sphero.wait(1) + sphero.setBackLEDIntensity(100) + sphero.stabilization(True) + + aOffset = node.calc_offset(sphero, cap, height) + + sphero.roll(100,0+aOffset) + sphero.wait(2) + sphero.roll(0,0) + + sphero.wait(5) + + targetPos = 117,430 + + node.drive_to(sphero,targetPos,aOffset,cap, height) + + rclpy.shutdown() + +if __name__ == '__main__': + main() + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_komisch.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_komisch.py new file mode 100644 index 0000000..7c5dc3e --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_komisch.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime +import cv2 +import time + + +# Img Objekt +cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + +# Set up the detector with default parameters. +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +#params.filterByArea = True +#params.minArea = 100 +#params.maxArea = 1000 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +#params.filterByInertia = True +#params.minInertiaRatio = 0.01 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) + +class MyNode(Node): + #Konstrukter Node Objekt + def __init__(self): + super().__init__("sphero_mini") + #self.publisher_ = self.create_publisher(String,"Imu",5) + + def connect(self): + #Automatisch MAC-Adresse laden + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + def positionauslesen(self): + zeitanfang = time.time() + + x = (-1) + y = (-1) + + while x < 0 and y < 0: + + success, img = cap.read() + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + # Detect blobs. + keypoints = detector.detect(gray) + + #print(keypoints) + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + + #print("Blob X: %f Blob Y: %f" %(x,y)) + zeitende = time.time() + print("Dauer Programmausführung:",(zeitende-zeitanfang)) + return (x,y) + + def ref_winkel(self, sphero): + + startpunkt = self.positionauslesen() + + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = self.positionauslesen() + + sphero.wait(0.5) + sphero.roll(100,180) + sphero.wait(2) + sphero.roll(0,0) + + soll = (startpunkt[0]+100,startpunkt[1]) + + soll = np.array(soll) + ref = np.array(ref) + startpunkt = np.array(startpunkt) + + start_ref = ref-startpunkt + start_soll = soll-startpunkt + + y = start_ref[0] * start_soll[1] - start_ref[1] * start_soll[0] + x = start_ref[0] * start_soll[0] + start_ref[1] * start_soll[1] + + phi = np.arctan2(y,x) + phi = np.degrees(phi) + phi = int(phi) + + if(phi < 0 ): + phi = phi + 360 + + return phi + + def fahre_ziel(self, sphero, x,y, phiAbw): + + startPos = self.positionauslesen() + sollPos = (x,y) + + startPos = np.array(startPos) + sollPos = np.array(sollPos) + + pktSpheroKord = sollPos - startPos + + phi = np.arctan2(pktSpheroKord[1], pktSpheroKord[0]) + phi = np.degrees(phi) + + phi = int(phi) + + phiZiel = phiAbw - phi + + if(phiZiel < 0 ): + phiZiel = phiZiel + 360 + + print("Winkel Sphero System ", phi) + + #sollPos = (x,y) + #startPos = self.positionauslesen() + #refPos = (startPos[0]+100,startPos[1]) + + #sollPos = np.array(sollPos) + #refPos = np.array(refPos) + #startPos = np.array(startPos) + + #start_ref = refPos-startPos + #start_soll = sollPos-startPos + + #y = start_ref[0] * start_soll[1] - start_ref[1] * start_soll[0] + #x = start_ref[0] * start_soll[0] + start_ref[1] * start_soll[1] + + #phi = np.arctan2(y,x) + #phi = np.degrees(phi) + #phiZiel = phiAbw - (int(phi) * -1) + + print("Zielwinkel: ", phiZiel) + + while(not(np.allclose(startPos, sollPos, atol = 50))): + + sphero.roll(100, (phiZiel)) + + aktPos = self.positionauslesen() + aktPos = np.array(aktPos) + + time.sleep(0.1) + + print("Ziel erreicht") + + sphero.roll(0, (0)) + + return + + +def main(args = None): + #Verbindung herstellen, Node erstellen + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(5) + + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + sphero.setLEDColor(255,0,0) + sphero.wait(1) + sphero.setBackLEDIntensity(100) + sphero.stabilization(True) + + #Differenzwinkel auslesen + phiAbw = node.ref_winkel(sphero) + print("Abweichender Winkel Initialfahrt:", phiAbw) + + sphero.wait(5) + sphero.roll(100, phiAbw) + + sphero.wait(5) + node.fahre_ziel(sphero, 94,94, phiAbw) + + sphero.wait(5) + node.fahre_ziel(sphero, 134,462, phiAbw) + + sphero.wait(5) + node.fahre_ziel(sphero, 581,372, phiAbw) + + sphero.wait(5) + node.fahre_ziel(sphero, 331,308, phiAbw) + + rclpy.spin() + rclpy.shutdown() + +if __name__ == '__main__': + main() + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_refwinkel.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_refwinkel.py new file mode 100644 index 0000000..78066a4 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_refwinkel.py @@ -0,0 +1,243 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime + +class MyNode(Node): + #Konstrukter Node Objekt + def __init__(self): + super().__init__("sphero_mini") + #self.publisher_ = self.create_publisher(String,"Imu",5) + + def connect(self): + #Automatisch MAC-Adresse laden + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + def positionauslesen(self): + # Standard imports + import cv2 + import numpy as np; + + # Img Objekt + cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + + # Set up the detector with default parameters. + detector = cv2.SimpleBlobDetector() + + # Setup SimpleBlobDetector parameters. + params = cv2.SimpleBlobDetector_Params() + + # Change thresholds + #params.minThreshold = 5 + #params.maxThreshold = 500 + + #FIlter by Color + params.filterByColor = True + params.blobColor = 255 + + # Filter by Area. + #params.filterByArea = True + #params.minArea = 100 + #params.maxArea = 1000 + + # Filter by Circularity + #params.filterByCircularity = True + #params.minCircularity = 0.5 + #params.maxCircularity = 1 + + # Filter by Convexity + #params.filterByConvexity = True + #params.minConvexity = 0.7 + #params.maxConvexity = 1 + + # Filter by Inertia + #params.filterByInertia = True + #params.minInertiaRatio = 0.01 + + # Create a detector with the parameters + detector = cv2.SimpleBlobDetector_create(params) + + success, img = cap.read() + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + + # Detect blobs. + keypoints = detector.detect(gray) + + #print(keypoints) + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + #keypointss = keyPoint.sizekeyPoints + + #print("Blob X: %f Blob Y: %f" %(x,y)) + + # Draw detected blobs as red circles. + # cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures + # the size of the circle corresponds to the size of blob + + im_with_keypoints = cv2.drawKeypoints(gray, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) + + # Show blobs + cv2.imshow("Keypoints", im_with_keypoints) + + return (x,y) + + def ref_winkel(self, sphero, x,y): + + startpunkt = self.positionauslesen() + + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = self.positionauslesen() + + #print(ref) + + sphero.wait(0.5) + sphero.roll(100,180) + sphero.wait(2) + sphero.roll(0,0) + + soll = (x,y) + + soll = np.array(soll) + ref = np.array(ref) + startpunkt = np.array(startpunkt) + + start_ref = ref-startpunkt + start_soll = soll-startpunkt + phi = np.arccos(np.dot(start_ref,start_soll) / (np.linalg.norm(start_ref)*np.linalg.norm(start_soll))) + return phi + +def main(args = None): + #Verbindung herstellen, Node erstellen + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(5) + + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + #Simple Moves, Erste Bewegung erkennen + #try:oll = (0,0) + # while rclpy.ok(): + + #sphero.positionauslesen() + #soll = 0,0 + phi = node.ref_winkel(sphero, 0,0) + + phi = np.degrees(phi) + + phi = int(phi) + + print("Abweihender Winkel:", phi) + + sphero.wait(0.5) + sphero.roll(100, (0 + phi)) + sphero.wait(2) + sphero.roll(0,0) + +# except KeyboardInterrupt: + # pass + + rclpy.shutdown() + # thread.join() + +if __name__ == '__main__': + main() + + #!/usr/bin/env python3 + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_testfahrt.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_testfahrt.py new file mode 100644 index 0000000..29f2473 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node_testfahrt.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 + +import rclpy +from rclpy.node import Node +import json +from sensor_msgs.msg import Imu +import threading +from std_msgs.msg import String +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from sphero_mini_controller.treiber import SpheroMini +import datetime + + +class MyNode(Node): + + def __init__(self): + super().__init__("sphero_mini") + self.publisher_ = self.create_publisher(String,"Imu",5) + #self.get_logger().info("Hello from ROS2") + + def connect(self): + #conf_file_path = file("/sphero_conf.json") + #with open("sphero_conf.json", "r") as f: + # cfg = Box(json.load(f)) + + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + #print(i) + + #self.publisher_.publish(i) + + + +def main(args = None): + + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + rate = node.create_rate(2) + + try: + while rclpy.ok(): + + sphero.configureSensorMask( + IMU_yaw=True, + IMU_pitch=True, + IMU_roll=True, + IMU_acc_y=True, + IMU_acc_z=True, + IMU_acc_x=True, + IMU_gyro_x=True, + IMU_gyro_y=True, + #IMU_gyro_z=True + ) + sphero.configureSensorStream() + + sensors_values = node.get_sensors_data(sphero) + #rclpy.logdebug(sensors_values) + node.publish_imu(sensors_values, node) + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn LEDs green + + # Aiming: + sphero.setLEDColor(red = 0, green = 0, blue = 0) # Turn main LED off + sphero.stabilization(False) # Turn off stabilization + sphero.setBackLEDIntensity(255) # turn back LED on + sphero.wait(3) # Non-blocking pau`se + sphero.resetHeading() # Reset heading + sphero.stabilization(True) # Turn on stabilization + sphero.setBackLEDIntensity(0) # Turn back LED off + + # Move around: + sphero.setLEDColor(red = 0, green = 0, blue = 255) # Turn main LED blue + sphero.roll(100, 0) # roll forwards (heading = 0) at speed = 50 + + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop + + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn main LED green + sphero.roll(-100, 0) # Keep facing forwards but roll backwards at speed = 50 + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop + + rate.sleep() + + except KeyboardInterrupt: + pass + + rclpy.shutdown() + thread.join() + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/pfadplanung.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/pfadplanung.py new file mode 100644 index 0000000..d07a2a0 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/pfadplanung.py @@ -0,0 +1,223 @@ + +def planInitialPath(start, goal): + + from matplotlib import pyplot as ppl + from matplotlib import cm + import random, sys, math, os.path + from matplotlib.pyplot import imread + + #Implementation of RRT + + MAP_IMG = './map.jpg' #Black and white image for a map + MIN_NUM_VERT = 20 # Minimum number of vertex in the graph + MAX_NUM_VERT = 1500 # Maximum number of vertex in the graph + STEP_DISTANCE = 10 # Maximum distance between two vertex + SEED = None # For random numbers + + def rapidlyExploringRandomTree(ax, img, start, goal, seed=None): + hundreds = 100 + seed = random.seed(seed) + #print("Zufallsseed: ", seed) + points = [] + graph = [] + points.append(start) + graph.append((start, [])) + print('Generating and conecting random points') + occupied = True + phaseTwo = False + + # Phase two values (points 5 step distances around the goal point) + minX = max(goal[0] - 5 * STEP_DISTANCE, 0) + maxX = min(goal[0] + 5 * STEP_DISTANCE, len(img[0]) - 1) + minY = max(goal[1] - 5 * STEP_DISTANCE, 0) + maxY = min(goal[1] + 5 * STEP_DISTANCE, len(img) - 1) + + i = 0 + while (goal not in points) and (len(points) < MAX_NUM_VERT): + if (i % 100) == 0: + print(i, 'points randomly generated') + + if (len(points) % hundreds) == 0: + print(len(points), 'vertex generated') + hundreds = hundreds + 100 + + while (occupied): + if phaseTwo and (random.random() > 0.8): + point = [random.randint(minX, maxX), random.randint(minY, maxY)] + else: + point = [random.randint(0, len(img[0]) - 1), random.randint(0, len(img) - 1)] + + if (img[point[1]][point[0]][0] * 255 == 255): + occupied = False + + occupied = True + + nearest = findNearestPoint(points, point) + newPoints = connectPoints(point, nearest, img) + addToGraph(ax, graph, newPoints, point) + newPoints.pop(0) # The first element is already in the points list + points.extend(newPoints) + ppl.draw() + i = i + 1 + + if len(points) >= MIN_NUM_VERT: + if not phaseTwo: + print('Phase Two') + phaseTwo = True + + if phaseTwo: + nearest = findNearestPoint(points, goal) + newPoints = connectPoints(goal, nearest, img) + addToGraph(ax, graph, newPoints, goal) + newPoints.pop(0) + points.extend(newPoints) + ppl.draw() + + if goal in points: + print('Goal found, total vertex in graph:', len(points), 'total random points generated:', i) + + path = searchPath(graph, start, [start]) + + for i in range(len(path) - 1): + ax.plot([path[i][0], path[i + 1][0]], [path[i][1], path[i + 1][1]], color='g', linestyle='-', + linewidth=2) + ppl.draw() + + print('Showing resulting map') + print('Final path:', path) + print('The final path is made from:', len(path), 'connected points') + else: + path = None + print('Reached maximum number of vertex and goal was not found') + print('Total vertex in graph:', len(points), 'total random points generated:', i) + print('Showing resulting map') + + ppl.show() + return path + + def searchPath(graph, point, path): + for i in graph: + if point == i[0]: + p = i + + if p[0] == graph[-1][0]: + return path + + for link in p[1]: + path.append(link) + finalPath = searchPath(graph, link, path) + + if finalPath != None: + return finalPath + else: + path.pop() + + def addToGraph(ax, graph, newPoints, point): + if len(newPoints) > 1: # If there is anything to add to the graph + for p in range(len(newPoints) - 1): + nearest = [nearest for nearest in graph if (nearest[0] == [newPoints[p][0], newPoints[p][1]])] + nearest[0][1].append(newPoints[p + 1]) + graph.append((newPoints[p + 1], [])) + + if not p == 0: + ax.plot(newPoints[p][0], newPoints[p][1], '+k') # First point is already painted + ax.plot([newPoints[p][0], newPoints[p + 1][0]], [newPoints[p][1], newPoints[p + 1][1]], color='k', + linestyle='-', linewidth=1) + + if point in newPoints: + ax.plot(point[0], point[1], '.g') # Last point is green + else: + ax.plot(newPoints[p + 1][0], newPoints[p + 1][1], '+k') # Last point is not green + + def connectPoints(a, b, img): + newPoints = [] + newPoints.append([b[0], b[1]]) + step = [(a[0] - b[0]) / float(STEP_DISTANCE), (a[1] - b[1]) / float(STEP_DISTANCE)] + + # Set small steps to check for walls + pointsNeeded = int(math.floor(max(math.fabs(step[0]), math.fabs(step[1])))) + + if math.fabs(step[0]) > math.fabs(step[1]): + if step[0] >= 0: + step = [1, step[1] / math.fabs(step[0])] + else: + step = [-1, step[1] / math.fabs(step[0])] + + else: + if step[1] >= 0: + step = [step[0] / math.fabs(step[1]), 1] + else: + step = [step[0] / math.fabs(step[1]), -1] + + blocked = False + for i in range(pointsNeeded + 1): # Creates points between graph and solitary point + for j in range(STEP_DISTANCE): # Check if there are walls between points + coordX = round(newPoints[i][0] + step[0] * j) + coordY = round(newPoints[i][1] + step[1] * j) + + if coordX == a[0] and coordY == a[1]: + break + if coordY >= len(img) or coordX >= len(img[0]): + break + if img[int(coordY)][int(coordX)][0] * 255 < 255: + blocked = True + if blocked: + break + + if blocked: + break + if not (coordX == a[0] and coordY == a[1]): + newPoints.append( + [newPoints[i][0] + (step[0] * STEP_DISTANCE), newPoints[i][1] + (step[1] * STEP_DISTANCE)]) + + if not blocked: + newPoints.append([a[0], a[1]]) + return newPoints + + def findNearestPoint(points, point): + best = (sys.maxsize, sys.maxsize, sys.maxsize) + for p in points: + if p == point: + continue + dist = math.sqrt((p[0] - point[0]) ** 2 + (p[1] - point[1]) ** 2) + if dist < best[2]: + best = (p[0], p[1], dist) + return (best[0], best[1]) + + # Standard imports + import cv2 + import numpy as np; + + # Img Objekt + cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + val, frame = cap.read() + inverted_image = np.invert(frame) + cv2.imwrite("map.jpg", inverted_image) + + print('Loading map... with file \'', MAP_IMG, '\'') + img = imread(MAP_IMG) + fig = ppl.gcf() + fig.clf() + ax = fig.add_subplot(1, 1, 1) + ax.imshow(img, cmap=cm.Greys_r) + ax.axis('image') + ppl.draw() + print('Map is', len(img[0]), 'x', len(img)) + + path = rapidlyExploringRandomTree(ax, img, start, goal, seed=SEED) + + if len(sys.argv) > 2: + print('Only one argument is needed') + elif len(sys.argv) > 1: + if os.path.isfile(sys.argv[1]): + MAP_IMG = sys.argv[1] + else: + print(sys.argv[1], 'is not a file') + + pathIndCntr = 0 + + return path, pathIndCntr + +start = (200,200) +ziel = (450, 450) +planInitialPath(start, ziel) \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/potential_field_planning (1).py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/potential_field_planning (1).py new file mode 100644 index 0000000..8f136b5 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/potential_field_planning (1).py @@ -0,0 +1,199 @@ +""" + +Potential Field based path planner + +author: Atsushi Sakai (@Atsushi_twi) + +Ref: +https://www.cs.cmu.edu/~motionplanning/lecture/Chap4-Potential-Field_howie.pdf + +""" + +from collections import deque +import numpy as np +import matplotlib.pyplot as plt + +# Parameters +KP = 5.0 # attractive potential gain +ETA = 100.0 # repulsive potential gain +AREA_WIDTH = 30.0 # potential area width [m] +# the number of previous positions used to check oscillations +OSCILLATIONS_DETECTION_LENGTH = 3 + +show_animation = True + + +def calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy): + minx = min(min(ox), sx, gx) - AREA_WIDTH / 2.0 + miny = min(min(oy), sy, gy) - AREA_WIDTH / 2.0 + maxx = max(max(ox), sx, gx) + AREA_WIDTH / 2.0 + maxy = max(max(oy), sy, gy) + AREA_WIDTH / 2.0 + xw = int(round((maxx - minx) / reso)) + yw = int(round((maxy - miny) / reso)) + + # calc each potential + pmap = [[0.0 for i in range(yw)] for i in range(xw)] + + for ix in range(xw): + x = ix * reso + minx + + for iy in range(yw): + y = iy * reso + miny + ug = calc_attractive_potential(x, y, gx, gy) + uo = calc_repulsive_potential(x, y, ox, oy, rr) + uf = ug + uo + pmap[ix][iy] = uf + + return pmap, minx, miny + + +def calc_attractive_potential(x, y, gx, gy): + return 0.5 * KP * np.hypot(x - gx, y - gy) + + +def calc_repulsive_potential(x, y, ox, oy, rr): + # search nearest obstacle + minid = -1 + dmin = float("inf") + for i, _ in enumerate(ox): + d = np.hypot(x - ox[i], y - oy[i]) + if dmin >= d: + dmin = d + minid = i + + # calc repulsive potential + dq = np.hypot(x - ox[minid], y - oy[minid]) + + if dq <= rr: + if dq <= 0.1: + dq = 0.1 + + return 0.5 * ETA * (1.0 / dq - 1.0 / rr) ** 2 + else: + return 0.0 + + +def get_motion_model(): + # dx, dy + motion = [[1, 0], + [0, 1], + [-1, 0], + [0, -1], + [-1, -1], + [-1, 1], + [1, -1], + [1, 1]] + + return motion + + +def oscillations_detection(previous_ids, ix, iy): + previous_ids.append((ix, iy)) + + if (len(previous_ids) > OSCILLATIONS_DETECTION_LENGTH): + previous_ids.popleft() + + # check if contains any duplicates by copying into a set + previous_ids_set = set() + for index in previous_ids: + if index in previous_ids_set: + return True + else: + previous_ids_set.add(index) + return False + + +def potential_field_planning(sx, sy, gx, gy, ox, oy, reso, rr): + + # calc potential field + pmap, minx, miny = calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy) + + # search path + d = np.hypot(sx - gx, sy - gy) + ix = round((sx - minx) / reso) + iy = round((sy - miny) / reso) + gix = round((gx - minx) / reso) + giy = round((gy - miny) / reso) + + if show_animation: + draw_heatmap(pmap) + # for stopping simulation with the esc key. + plt.gcf().canvas.mpl_connect('key_release_event', + lambda event: [exit(0) if event.key == 'escape' else None]) + plt.plot(ix, iy, "*k") + plt.plot(gix, giy, "*m") + + rx, ry = [sx], [sy] + motion = get_motion_model() + previous_ids = deque() + + while d >= reso: + minp = float("inf") + minix, miniy = -1, -1 + for i, _ in enumerate(motion): + inx = int(ix + motion[i][0]) + iny = int(iy + motion[i][1]) + if inx >= len(pmap) or iny >= len(pmap[0]) or inx < 0 or iny < 0: + p = float("inf") # outside area + print("outside potential!") + else: + p = pmap[inx][iny] + if minp > p: + minp = p + minix = inx + miniy = iny + ix = minix + iy = miniy + xp = ix * reso + minx + yp = iy * reso + miny + d = np.hypot(gx - xp, gy - yp) + rx.append(xp) + ry.append(yp) + + if (oscillations_detection(previous_ids, ix, iy)): + print("Oscillation detected at ({},{})!".format(ix, iy)) + break + + if show_animation: + plt.plot(ix, iy, ".r") + plt.pause(0.01) + + print("Goal!!") + + return rx, ry + + +def draw_heatmap(data): + data = np.array(data).T + plt.pcolor(data, vmax=100.0, cmap=plt.cm.Blues) + + +def main(): + print("potential_field_planning start") + + sx = 0.0 # start x position [m] + sy = 10.0 # start y positon [m] + gx = 30.0 # goal x position [m] + gy = 30.0 # goal y position [m] + grid_size = 0.5 # potential grid size [m] + robot_radius = 5.0 # robot radius [m] + + ox = [15.0, 5.0, 20.0, 25.0] # obstacle x position list [m] + oy = [25.0, 15.0, 26.0, 25.0] # obstacle y position list [m] + + if show_animation: + plt.grid(True) + plt.axis("equal") + + # path generation + _, _ = potential_field_planning( + sx, sy, gx, gy, ox, oy, grid_size, robot_radius) + + if show_animation: + plt.show() + + +if __name__ == '__main__': + print(__file__ + " start!!") + main() + print(__file__ + " Done!!") diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/potential_field_planning.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/potential_field_planning.py new file mode 100644 index 0000000..c28279d --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/potential_field_planning.py @@ -0,0 +1,248 @@ +""" + +Potential Field based path planner + +author: Atsushi Sakai (@Atsushi_twi) + +Ref: +https://www.cs.cmu.edu/~motionplanning/lecture/Chap4-Potential-Field_howie.pdf + +""" + +from collections import deque +import numpy as np +import matplotlib.pyplot as plt + +# Parameters +KP = 10.0 # attractive potential gain +ETA = 300.0 # repulsive potential gain +AREA_WIDTH = 704.0 # potential area width [m] +# the number of previous positions used to check oscillations +OSCILLATIONS_DETECTION_LENGTH = 3 + +show_animation = True + +px = [] +py = [] + +def calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy): + minx = min(min(ox), sx, gx) - AREA_WIDTH / 2.0 + miny = min(min(oy), sy, gy) - AREA_WIDTH / 2.0 + maxx = max(max(ox), sx, gx) + AREA_WIDTH / 2.0 + maxy = max(max(oy), sy, gy) + AREA_WIDTH / 2.0 + xw = int(round((maxx - minx) / reso)) + yw = int(round((maxy - miny) / reso)) + + # calc each potential + pmap = [[0.0 for i in range(yw)] for i in range(xw)] + + for ix in range(xw): + x = ix * reso + minx + + for iy in range(yw): + y = iy * reso + miny + ug = calc_attractive_potential(x, y, gx, gy) + uo = calc_repulsive_potential(x, y, ox, oy, rr) + uf = ug + uo + pmap[ix][iy] = uf + + return pmap, minx, miny + + +def calc_attractive_potential(x, y, gx, gy): + return 0.5 * KP * np.hypot(x - gx, y - gy) + + +def calc_repulsive_potential(x, y, ox, oy, rr): + # search nearest obstacle + minid = -1 + dmin = float("inf") + for i, _ in enumerate(ox): + d = np.hypot(x - ox[i], y - oy[i]) + if dmin >= d: + dmin = d + minid = i + + # calc repulsive potential + dq = np.hypot(x - ox[minid], y - oy[minid]) + + if dq <= rr: + if dq <= 0.1: + dq = 0.1 + + return 0.5 * ETA * (1.0 / dq - 1.0 / rr) ** 2 + else: + return 0.0 + + +def get_motion_model(): + # dx, dy + motion = [[1, 0], + [0, 1], + [-1, 0], + [0, -1], + [-1, -1], + [-1, 1], + [1, -1], + [1, 1]] + + return motion + + +def oscillations_detection(previous_ids, ix, iy): + previous_ids.append((ix, iy)) + + if (len(previous_ids) > OSCILLATIONS_DETECTION_LENGTH): + previous_ids.popleft() + + # check if contains any duplicates by copying into a set + previous_ids_set = set() + for index in previous_ids: + if index in previous_ids_set: + return True + else: + previous_ids_set.add(index) + return False + + +def potential_field_planning(sx, sy, gx, gy, ox, oy, reso, rr): + + # calc potential field + pmap, minx, miny = calc_potential_field(gx, gy, ox, oy, reso, rr, sx, sy) + + # search path + d = np.hypot(sx - gx, sy - gy) + ix = round((sx - minx) / reso) + iy = round((sy - miny) / reso) + gix = round((gx - minx) / reso) + giy = round((gy - miny) / reso) + + if show_animation: + draw_heatmap(pmap) + # for stopping simulation with the esc key. + plt.gcf().canvas.mpl_connect('key_release_event', + lambda event: [exit(0) if event.key == 'escape' else None]) + plt.plot(ix, iy, "*k") + plt.plot(gix, giy, "*m") + + rx, ry = [sx], [sy] + motion = get_motion_model() + previous_ids = deque() + + while d >= reso: + minp = float("inf") + minix, miniy = -1, -1 + for i, _ in enumerate(motion): + inx = int(ix + motion[i][0]) + iny = int(iy + motion[i][1]) + if inx >= len(pmap) or iny >= len(pmap[0]) or inx < 0 or iny < 0: + p = float("inf") # outside area + print("outside potential!") + else: + p = pmap[inx][iny] + if minp > p: + minp = p + minix = inx + miniy = iny + ix = minix + iy = miniy + xp = ix * reso + minx + yp = iy * reso + miny + d = np.hypot(gx - xp, gy - yp) + rx.append(xp) + ry.append(yp) + + if (oscillations_detection(previous_ids, ix, iy)): + print("Oscillation detected at ({},{})!".format(ix, iy)) + break + + if show_animation: + px.append(ix) + py.append(iy) + plt.plot(ix, iy, ".r") + plt.pause(0.01) + + print("Goal!!") + + return rx, ry + + +def draw_heatmap(data): + data = np.array(data).T + plt.pcolor(data, vmax=100.0, cmap=plt.cm.Blues) + + +def create_map(): + # Map erstellen + # Standard imports + import cv2 + import numpy as np; + + # Img Objekt + #cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + + #success, img = cap.read() + + gray = cv2.imread("/home/ubuntu/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/map/map.jpg",cv2.IMREAD_GRAYSCALE) + + #gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) + mask = cv2.inRange(gray, np.array([90]), np.array([255])) + + mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + + mask_list= np.ndarray.tolist(mask) + + for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['W' if j > 200 else ' ' for j in mask_list[i]] + + #mapOfWorld[200][200] = 'R' #Start Pos + #mapOfWorld[300][300] = 'G' #Ziel Pos + + return mapOfWorld + + +def create_obsticles(mapOfWorld): + ox = [] + oy = [] + + mapOfWorld = np.array(mapOfWorld) + + for i in range(0,mapOfWorld.shape[0]): + for j in range(0,mapOfWorld.shape[1]): + if mapOfWorld[i][j] == 'W': + ox.append(i) + oy.append(j) + + return ox,oy + +def main(): + print("potential_field_planning start") + + mapOfWorld = create_map() + + sx = 50 # start x position [m] + sy = 400 # start y positon [m] + gx = 600 # goal x position [m] + gy = 100 # goal y position [m] + grid_size = 10 # potential grid size [m] + robot_radius = 10 # robot radius [m] + + ox, oy = create_obsticles(mapOfWorld) + + if show_animation: + plt.grid(True) + plt.axis("equal") + + # path generation + _, _ = potential_field_planning(sx, sy, gx, gy, ox, oy, grid_size, robot_radius) + + if show_animation: + plt.show() + + print("X_Pos", px) + print("Y_Pos", py) + +if __name__ == '__main__': + print(__file__ + " start!!") + main() + print(__file__ + " Done!!") diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/simple_moves.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/simple_moves.py new file mode 100644 index 0000000..b1e9f0e --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/simple_moves.py @@ -0,0 +1,34 @@ +""" +Simple moves +------------ + +This module contains functions to have the Sphero mini do some simple movements (e.g. circle). +""" + +import sys + +def forward(sphero): + # Aiming: + sphero.setLEDColor(red = 0, green = 0, blue = 0) # Turn main LED off + sphero.stabilization(False) # Turn off stabilization + sphero.setBackLEDIntensity(255) # turn back LED on + sphero.wait(3) # Non-blocking pau`se + sphero.resetHeading() # Reset heading + sphero.stabilization(True) # Turn on stabilization + sphero.setBackLEDIntensity(0) # Turn back LED off + + # Move around: + sphero.setLEDColor(red = 0, green = 0, blue = 255) # Turn main LED blue + sphero.roll(100, 0) # roll forwards (heading = 0) at speed = 50 + + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop + + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn main LED green + sphero.roll(-100, 0) # Keep facing forwards but roll backwards at speed = 50 + sphero.wait(3) # Keep rolling for three seconds + + sphero.roll(0, 0) # stop + sphero.wait(1) # Allow time to stop diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/sphero.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/sphero.py new file mode 100644 index 0000000..b5138d1 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/sphero.py @@ -0,0 +1,22 @@ +import numpy as np +def ref_winkel(sphero, soll): + + startpunkt = sphero.positionsauslesen + + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = sphero.positionsauslesen + + sphero.wait(0.5) + sphero.roll(100,0) + sphero.wait(2) + sphero.roll(0,0) + + start_ref = ref-startpunkt + start_soll = soll-startpunkt + phi = np.arccos(start_ref*start_soll / (np.linalg.norm(start_ref)*np.linalg.norm(start_soll))) + return phi + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/sphero_main.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/sphero_main.py new file mode 100644 index 0000000..37e337b --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/sphero_main.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 + +import json + +import rclpy +from rclpy.node import Node +from sensor_msgs.msg import Imu +from geometry_msgs.msg import Quaternion, Vector3 +from box import Box +import numpy as np +from core import SpheroMini +#from simple_moves import forward + +def connect(): + conf_file_path = rclpy.get_param("/conf_file_path") + with open(conf_file_path, 'r') as f: + cfg = Box(json.load(f)) + + # Connect: + sphero = SpheroMini(cfg.MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + +def disconnect(sphero): + sphero.sleep() + sphero.disconnect() + + +def create_quaternion(roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + +def create_angular_veolocity_vector3(groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = gyaw + + return v + + +def create_linear_acc_vector3(xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + +def get_sensors_data(sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + +def publish_imu(pub, sensors_values): + i = Imu() + + i.header.stamp = rclpy.Time.now() + i.orientation = create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + pub.publish(i) + + +def main(sphero): + rclpy.init_node('sphero', anonymous=True, log_level=rclpy.DEBUG) + rate = rclpy.Rate(10) # 10 Hz + + pub = rclpy.Publisher("/imu", Imu, queue_size=5) + + sphero.configureSensorMask( + IMU_yaw=True, + IMU_pitch=True, + IMU_roll=True, + IMU_acc_y=True, + IMU_acc_z=True, + IMU_acc_x=True, + IMU_gyro_x=True, + IMU_gyro_y=True, + #IMU_gyro_z=True + ) + sphero.configureSensorStream() + + while not rclpy.is_shutdown(): + sensors_values = get_sensors_data(sphero) + #rclpy.logdebug(sensors_values) + publish_imu(pub, sensors_values) + sphero.setLEDColor(red = 0, green = 255, blue = 0) # Turn LEDs green + rate.sleep() + + +if __name__ == "__main__": + sphero = connect() + try: + main(sphero) + except Exception as e: # rclpy.ROSInterruptException + disconnect(sphero) + raise e + \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/test.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/test.py new file mode 100644 index 0000000..b5c4cbb --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/test.py @@ -0,0 +1,23 @@ +#Map erstellen +# Standard imports +import cv2 +import numpy as np; + +# Img Objekt +cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + +success, img = cap.read() + +gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) +mask = cv2.inRange(gray, np.array([90]), np.array([255])) + +mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + +mask_list= np.ndarray.tolist(mask) + +for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['W' if j > 200 else ' ' for j in mask_list[i]] + +print(mapOfWorld) + + diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/treiber.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/treiber.py new file mode 100644 index 0000000..ec34350 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/treiber.py @@ -0,0 +1,638 @@ +from bluepy.btle import Peripheral +from bluepy import btle +from sphero_mini_controller._constants import * +import struct +import time +import sys + +class SpheroMini(): + def __init__(self, MACAddr, verbosity = 4, user_delegate = None): + ''' + initialize class instance and then build collect BLE sevices and characteristics. + Also sends text string to Anti-DOS characteristic to prevent returning to sleep, + and initializes notifications (which is what the sphero uses to send data back to + the client). + ''' + self.verbosity = verbosity # 0 = Silent, + # 1 = Connection/disconnection only + # 2 = Init messages + # 3 = Recieved commands + # 4 = Acknowledgements + self.sequence = 1 + self.v_batt = None # will be updated with battery voltage when sphero.getBatteryVoltage() is called + self.firmware_version = [] # will be updated with firware version when sphero.returnMainApplicationVersion() is called + + if self.verbosity > 0: + print("[INFO] Connecting to ", MACAddr) + self.p = Peripheral(MACAddr, "random") #connect + + if self.verbosity > 1: + print("[INIT] Initializing") + + # Subscribe to notifications + self.sphero_delegate = MyDelegate(self, user_delegate) # Pass a reference to this instance when initializing + self.p.setDelegate(self.sphero_delegate) + + if self.verbosity > 1: + print("[INIT] Read all characteristics and descriptors") + # Get characteristics and descriptors + self.API_V2_characteristic = self.p.getCharacteristics(uuid="00010002-574f-4f20-5370-6865726f2121")[0] + self.AntiDOS_characteristic = self.p.getCharacteristics(uuid="00020005-574f-4f20-5370-6865726f2121")[0] + self.DFU_characteristic = self.p.getCharacteristics(uuid="00020002-574f-4f20-5370-6865726f2121")[0] + self.DFU2_characteristic = self.p.getCharacteristics(uuid="00020004-574f-4f20-5370-6865726f2121")[0] + self.API_descriptor = self.API_V2_characteristic.getDescriptors(forUUID=0x2902)[0] + self.DFU_descriptor = self.DFU_characteristic.getDescriptors(forUUID=0x2902)[0] + + # The rest of this sequence was observed during bluetooth sniffing: + # Unlock code: prevent the sphero mini from going to sleep again after 10 seconds + if self.verbosity > 1: + print("[INIT] Writing AntiDOS characteristic unlock code") + self.AntiDOS_characteristic.write("usetheforce...band".encode(), withResponse=True) + + # Enable DFU notifications: + if self.verbosity > 1: + print("[INIT] Configuring DFU descriptor") + self.DFU_descriptor.write(struct.pack('<bb', 0x01, 0x00), withResponse = True) + + # No idea what this is for. Possibly a device ID of sorts? Read request returns '00 00 09 00 0c 00 02 02': + if self.verbosity > 1: + print("[INIT] Reading DFU2 characteristic") + _ = self.DFU2_characteristic.read() + + # Enable API notifications: + if self.verbosity > 1: + print("[INIT] Configuring API dectriptor") + self.API_descriptor.write(struct.pack('<bb', 0x01, 0x00), withResponse = True) + + self.wake() + + # Finished initializing: + if self.verbosity > 1: + print("[INIT] Initialization complete\n") + + def disconnect(self): + if self.verbosity > 0: + print("[INFO] Disconnecting") + + self.p.disconnect() + + def wake(self): + ''' + Bring device out of sleep mode (can only be done if device was in sleep, not deep sleep). + If in deep sleep, the device should be connected to USB power to wake. + ''' + if self.verbosity > 2: + print("[SEND {}] Waking".format(self.sequence)) + + self._send(characteristic=self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=powerCommandIDs["wake"], + payload=[]) # empty payload + + self.getAcknowledgement("Wake") + + def sleep(self, deepSleep=False): + ''' + Put device to sleep or deep sleep (deep sleep needs USB power connected to wake up) + ''' + if deepSleep: + sleepCommID=powerCommandIDs["deepSleep"] + if self.verbosity > 0: + print("[INFO] Going into deep sleep. Connect USB power to wake.") + else: + sleepCommID=powerCommandIDs["sleep"] + self._send(characteristic=self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=sleepCommID, + payload=[]) #empty payload + + def setLEDColor(self, red = None, green = None, blue = None): + ''' + Set device LED color based on RGB vales (each can range between 0 and 0xFF) + ''' + if self.verbosity > 2: + print("[SEND {}] Setting main LED colour to [{}, {}, {}]".format(self.sequence, red, green, blue)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['userIO'], # 0x1a + commID = userIOCommandIDs["allLEDs"], # 0x0e + payload = [0x00, 0x0e, red, green, blue]) + + self.getAcknowledgement("LED/backlight") + + def setBackLEDIntensity(self, brightness=None): + ''' + Set device LED backlight intensity based on 0-255 values + + NOTE: this is not the same as aiming - it only turns on the LED + ''' + if self.verbosity > 2: + print("[SEND {}] Setting backlight intensity to {}".format(self.sequence, brightness)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['userIO'], + commID = userIOCommandIDs["allLEDs"], + payload = [0x00, 0x01, brightness]) + + self.getAcknowledgement("LED/backlight") + + def roll(self, speed=None, heading=None): + ''' + Start to move the Sphero at a given direction and speed. + heading: integer from 0 - 360 (degrees) + speed: Integer from 0 - 255 + + Note: the zero heading should be set at startup with the resetHeading method. Otherwise, it may + seem that the sphero doesn't honor the heading argument + ''' + if self.verbosity > 2: + print("[SEND {}] Rolling with speed {} and heading {}".format(self.sequence, speed, heading)) + + if abs(speed) > 255: + print("WARNING: roll speed parameter outside of allowed range (-255 to +255)") + + if speed < 0: + speed = -1*speed+256 # speed values > 256 in the send packet make the spero go in reverse + + speedH = (speed & 0xFF00) >> 8 + speedL = speed & 0xFF + headingH = (heading & 0xFF00) >> 8 + headingL = heading & 0xFF + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['driving'], + commID = drivingCommands["driveWithHeading"], + payload = [speedL, headingH, headingL, speedH]) + + self.getAcknowledgement("Roll") + + def resetHeading(self): + ''' + Reset the heading zero angle to the current heading (useful during aiming) + Note: in order to manually rotate the sphero, you need to call stabilization(False). + Once the heading has been set, call stabilization(True). + ''' + if self.verbosity > 2: + print("[SEND {}] Resetting heading".format(self.sequence)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['driving'], + commID = drivingCommands["resetHeading"], + payload = []) #empty payload + + self.getAcknowledgement("Heading") + + def returnMainApplicationVersion(self): + ''' + Sends command to return application data in a notification + ''' + if self.verbosity > 2: + print("[SEND {}] Requesting firmware version".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID = deviceID['systemInfo'], + commID = SystemInfoCommands['mainApplicationVersion'], + payload = []) # empty + + self.getAcknowledgement("Firmware") + + def getBatteryVoltage(self): + ''' + Sends command to return battery voltage data in a notification. + Data printed to console screen by the handleNotifications() method in the MyDelegate class. + ''' + if self.verbosity > 2: + print("[SEND {}] Requesting battery voltage".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=powerCommandIDs['batteryVoltage'], + payload=[]) # empty + + self.getAcknowledgement("Battery") + + def stabilization(self, stab = True): + ''' + Sends command to turn on/off the motor stabilization system (required when manually turning/aiming the sphero) + ''' + if stab == True: + if self.verbosity > 2: + print("[SEND {}] Enabling stabilization".format(self.sequence)) + val = 1 + else: + if self.verbosity > 2: + print("[SEND {}] Disabling stabilization".format(self.sequence)) + val = 0 + self._send(self.API_V2_characteristic, + devID=deviceID['driving'], + commID=drivingCommands['stabilization'], + payload=[val]) + + self.getAcknowledgement("Stabilization") + + def wait(self, delay): + ''' + This is a non-blocking delay command. It is similar to time.sleep(), except it allows asynchronous + notification handling to still be performed. + ''' + start = time.time() + while(1): + self.p.waitForNotifications(0.001) + if time.time() - start > delay: + break + + def _send(self, characteristic=None, devID=None, commID=None, payload=[]): + ''' + A generic "send" method, which will be used by other methods to send a command ID, payload and + appropriate checksum to a specified device ID. Mainly useful because payloads are optional, + and can be of varying length, to convert packets to binary, and calculate and send the + checksum. For internal use only. + + Packet structure has the following format (in order): + + - Start byte: always 0x8D + - Flags byte: indicate response required, etc + - Virtual device ID: see _constants.py + - Command ID: see _constants.py + - Sequence number: Seems to be arbitrary. I suspect it is used to match commands to response packets (in which the number is echoed). + - Payload: Could be varying number of bytes (incl. none), depending on the command + - Checksum: See below for calculation + - End byte: always 0xD8 + + ''' + sendBytes = [sendPacketConstants["StartOfPacket"], + sum([flags["resetsInactivityTimeout"], flags["requestsResponse"]]), + devID, + commID, + self.sequence] + payload # concatenate payload list + + self.sequence += 1 # Increment sequence number, ensures we can identify response packets are for this command + if self.sequence > 255: + self.sequence = 0 + + # Compute and append checksum and add EOP byte: + # From Sphero docs: "The [checksum is the] modulo 256 sum of all the bytes + # from the device ID through the end of the data payload, + # bit inverted (1's complement)" + # For the sphero mini, the flag bits must be included too: + checksum = 0 + for num in sendBytes[1:]: + checksum = (checksum + num) & 0xFF # bitwise "and to get modulo 256 sum of appropriate bytes + checksum = 0xff - checksum # bitwise 'not' to invert checksum bits + sendBytes += [checksum, sendPacketConstants["EndOfPacket"]] # concatenate + + # Convert numbers to bytes + output = b"".join([x.to_bytes(1, byteorder='big') for x in sendBytes]) + + #send to specified characteristic: + characteristic.write(output, withResponse = True) + + def getAcknowledgement(self, ack): + #wait up to 10 secs for correct acknowledgement to come in, including sequence number! + start = time.time() + while(1): + self.p.waitForNotifications(1) + if self.sphero_delegate.notification_seq == self.sequence-1: # use one less than sequence, because _send function increments it for next send. + if self.verbosity > 3: + print("[RESP {}] {}".format(self.sequence-1, self.sphero_delegate.notification_ack)) + self.sphero_delegate.clear_notification() + break + elif self.sphero_delegate.notification_seq >= 0: + print("Unexpected ACK. Expected: {}/{}, received: {}/{}".format( + ack, self.sequence, self.sphero_delegate.notification_ack.split()[0], + self.sphero_delegate.notification_seq) + ) + if time.time() > start + 10: + print("Timeout waiting for acknowledgement: {}/{}".format(ack, self.sequence)) + break + +# ======================================================================= +# The following functions are experimental: +# ======================================================================= + + def configureCollisionDetection(self, + xThreshold = 50, + yThreshold = 50, + xSpeed = 50, + ySpeed = 50, + deadTime = 50, # in 10 millisecond increments + method = 0x01, # Must be 0x01 + callback = None): + ''' + Appears to function the same as other Sphero models, however speed settings seem to have no effect. + NOTE: Setting to zero seems to cause bluetooth errors with the Sphero Mini/bluepy library - set to + 255 to make it effectively disabled. + + deadTime disables future collisions for a short period of time to avoid repeat triggering by the same + event. Set in 10ms increments. So if deadTime = 50, that means the delay will be 500ms, or half a second. + + From Sphero docs: + + xThreshold/yThreshold: An 8-bit settable threshold for the X (left/right) and Y (front/back) axes + of Sphero. + + xSpeed/ySpeed: An 8-bit settable speed value for the X and Y axes. This setting is ranged by the + speed, then added to xThreshold, yThreshold to generate the final threshold value. + ''' + + if self.verbosity > 2: + print("[SEND {}] Configuring collision detection".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['configureCollision'], + payload=[method, xThreshold, xSpeed, yThreshold, ySpeed, deadTime]) + + self.collision_detection_callback = callback + + self.getAcknowledgement("Collision") + + def configureSensorStream(self): # Use default values + ''' + Send command to configure sensor stream using default values as found during bluetooth + sniffing of the Sphero Edu app. + + Must be called after calling configureSensorMask() + ''' + bitfield1 = 0b00000000 # Unknown function - needs experimenting + bitfield2 = 0b00000000 # Unknown function - needs experimenting + bitfield3 = 0b00000000 # Unknown function - needs experimenting + bitfield4 = 0b00000000 # Unknown function - needs experimenting + + if self.verbosity > 2: + print("[SEND {}] Configuring sensor stream".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['configureSensorStream'], + payload=[bitfield1, bitfield1, bitfield1, bitfield1]) + + self.getAcknowledgement("Sensor") + + def configureSensorMask(self, + sample_rate_divisor = 0x25, # Must be > 0 + packet_count = 0, + IMU_pitch = False, + IMU_roll = False, + IMU_yaw = False, + IMU_acc_x = False, + IMU_acc_y = False, + IMU_acc_z = False, + IMU_gyro_x = False, + IMU_gyro_y = False, + IMU_gyro_z = False): + + ''' + Send command to configure sensor mask using default values as found during bluetooth + sniffing of the Sphero Edu app. From experimentation, it seems that these are he functions of each: + + Sampling_rate_divisor. Slow data EG: Set to 0x32 to the divide data rate by 50. Setting below 25 (0x19) causes + bluetooth errors + + Packet_count: Select the number of packets to transmit before ending the stream. Set to zero to stream infinitely + + All IMU bool parameters: Toggle transmission of that value on or off (e.g. set IMU_acc_x = True to include the + X-axis accelerometer readings in the sensor stream) + ''' + + # Construct bitfields based on function parameters: + IMU_bitfield1 = (IMU_pitch<<2) + (IMU_roll<<1) + IMU_yaw + IMU_bitfield2 = ((IMU_acc_y<<7) + (IMU_acc_z<<6) + (IMU_acc_x<<5) + \ + (IMU_gyro_y<<4) + (IMU_gyro_x<<2) + (IMU_gyro_z<<2)) + + if self.verbosity > 2: + print("[SEND {}] Configuring sensor mask".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensorMask'], + payload=[0x00, # Unknown param - altering it seems to slow data rate. Possibly averages multiple readings? + sample_rate_divisor, + packet_count, # Packet count: select the number of packets to stop streaming after (zero = infinite) + 0b00, # Unknown param: seems to be another accelerometer bitfield? Z-acc, Y-acc + IMU_bitfield1, + IMU_bitfield2, + 0b00]) # reserved, Position?, Position?, velocity?, velocity?, Y-gyro, timer, reserved + + self.getAcknowledgement("Mask") + + ''' + Since the sensor values arrive as unlabelled lists in the order that they appear in the bitfields above, we need + to create a list of sensors that have been configured.Once we have this list, then in the default_delegate class, + we can get sensor values as attributes of the sphero_mini class. + e.g. print(sphero.IMU_yaw) # displays the current yaw angle + ''' + + # Initialize dictionary with sensor names as keys and their bool values (set by the user) as values: + availableSensors = {"IMU_pitch" : IMU_pitch, + "IMU_roll" : IMU_roll, + "IMU_yaw" : IMU_yaw, + "IMU_acc_y" : IMU_acc_y, + "IMU_acc_z" : IMU_acc_z, + "IMU_acc_x" : IMU_acc_x, + "IMU_gyro_y" : IMU_gyro_y, + "IMU_gyro_x" : IMU_gyro_x, + "IMU_gyro_z" : IMU_gyro_z} + + # Create list of of only sensors that have been "activated" (set as true in the method arguments): + self.configured_sensors = [name for name in availableSensors if availableSensors[name] == True] + + def sensor1(self): # Use default values + ''' + Unknown function. Observed in bluetooth sniffing. + ''' + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensor1'], + payload=[0x01]) + + self.getAcknowledgement("Sensor1") + + def sensor2(self): # Use default values + ''' + Unknown function. Observed in bluetooth sniffing. + ''' + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensor2'], + payload=[0x00]) + + self.getAcknowledgement("Sensor2") + +# ======================================================================= + +class MyDelegate(btle.DefaultDelegate): + + ''' + This class handles notifications (both responses and asynchronous notifications). + + Usage of this class is described in the Bluepy documentation + + ''' + + def __init__(self, sphero_class, user_delegate): + self.sphero_class = sphero_class # for saving sensor values as attributes of sphero class instance + self.user_delegate = user_delegate # to directly notify users of callbacks + btle.DefaultDelegate.__init__(self) + self.clear_notification() + self.notificationPacket = [] + + def clear_notification(self): + self.notification_ack = "DEFAULT ACK" + self.notification_seq = -1 + + def bits_to_num(self, bits): + ''' + This helper function decodes bytes from sensor packets into single precision floats. Encoding follows the + the IEEE-754 standard. + ''' + num = int(bits, 2).to_bytes(len(bits) // 8, byteorder='little') + num = struct.unpack('f', num)[0] + return num + + def handleNotification(self, cHandle, data): + ''' + This method acts as an interrupt service routine. When a notification comes in, this + method is invoked, with the variable 'cHandle' being the handle of the characteristic that + sent the notification, and 'data' being the payload (sent one byte at a time, so the packet + needs to be reconstructed) + + The method keeps appending bytes to the payload packet byte list until end-of-packet byte is + encountered. Note that this is an issue, because 0xD8 could be sent as part of the payload of, + say, the battery voltage notification. In future, a more sophisticated method will be required. + ''' + # Allow the user to intercept and process data first.. + if self.user_delegate != None: + if self.user_delegate.handleNotification(cHandle, data): + return + + #print("Received notification with packet ", str(data)) + + for data_byte in data: # parse each byte separately (sometimes they arrive simultaneously) + + self.notificationPacket.append(data_byte) # Add new byte to packet list + + # If end of packet (need to find a better way to segment the packets): + if data_byte == sendPacketConstants['EndOfPacket']: + # Once full the packet has arrived, parse it: + # Packet structure is similar to the outgoing send packets (see docstring in sphero_mini._send()) + + # Attempt to unpack. Might fail if packet is too badly corrupted + try: + start, flags_bits, devid, commcode, seq, *notification_payload, chsum, end = self.notificationPacket + except ValueError: + print("Warning: notification packet unparseable ", self.notificationPacket) + self.notificationPacket = [] # Discard this packet + return # exit + + # Compute and append checksum and add EOP byte: + # From Sphero docs: "The [checksum is the] modulo 256 sum of all the bytes + # from the device ID through the end of the data payload, + # bit inverted (1's complement)" + # For the sphero mini, the flag bits must be included too: + checksum_bytes = [flags_bits, devid, commcode, seq] + notification_payload + checksum = 0 # init + for num in checksum_bytes: + checksum = (checksum + num) & 0xFF # bitwise "and to get modulo 256 sum of appropriate bytes + checksum = 0xff - checksum # bitwise 'not' to invert checksum bits + if checksum != chsum: # check computed checksum against that recieved in the packet + print("Warning: notification packet checksum failed - ", str(self.notificationPacket)) + self.notificationPacket = [] # Discard this packet + return # exit + + # Check if response packet: + if flags_bits & flags['isResponse']: # it is a response + + # Use device ID and command code to determine which command is being acknowledged: + if devid == deviceID['powerInfo'] and commcode == powerCommandIDs['wake']: + self.notification_ack = "Wake acknowledged" # Acknowledgement after wake command + + elif devid == deviceID['driving'] and commcode == drivingCommands['driveWithHeading']: + self.notification_ack = "Roll command acknowledged" + + elif devid == deviceID['driving'] and commcode == drivingCommands['stabilization']: + self.notification_ack = "Stabilization command acknowledged" + + elif devid == deviceID['userIO'] and commcode == userIOCommandIDs['allLEDs']: + self.notification_ack = "LED/backlight color command acknowledged" + + elif devid == deviceID['driving'] and commcode == drivingCommands["resetHeading"]: + self.notification_ack = "Heading reset command acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["configureCollision"]: + self.notification_ack = "Collision detection configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["configureSensorStream"]: + self.notification_ack = "Sensor stream configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensorMask"]: + self.notification_ack = "Mask configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensor1"]: + self.notification_ack = "Sensor1 acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensor2"]: + self.notification_ack = "Sensor2 acknowledged" + + elif devid == deviceID['powerInfo'] and commcode == powerCommandIDs['batteryVoltage']: + V_batt = notification_payload[2] + notification_payload[1]*256 + notification_payload[0]*65536 + V_batt /= 100 # Notification gives V_batt in 10mV increments. Divide by 100 to get to volts. + self.notification_ack = "Battery voltage:" + str(V_batt) + "v" + self.sphero_class.v_batt = V_batt + + elif devid == deviceID['systemInfo'] and commcode == SystemInfoCommands['mainApplicationVersion']: + version = '.'.join(str(x) for x in notification_payload) + self.notification_ack = "Firmware version: " + version + self.sphero_class.firmware_version = notification_payload + + else: + self.notification_ack = "Unknown acknowledgement" #print(self.notificationPacket) + print(self.notificationPacket, "===================> Unknown ack packet") + + self.notification_seq = seq + + else: # Not a response packet - therefore, asynchronous notification (e.g. collision detection, etc): + + # Collision detection: + if devid == deviceID['sensor'] and commcode == sensorCommands['collisionDetectedAsync']: + # The first four bytes are data that is still un-parsed. the remaining unsaved bytes are always zeros + _, _, _, _, _, _, axis, _, Y_mag, _, X_mag, *_ = notification_payload + if axis == 1: + dir = "Left/right" + else: + dir = 'Forward/back' + print("Collision detected:") + print("\tAxis: ", dir) + print("\tX_mag: ", X_mag) + print("\tY_mag: ", Y_mag) + + if self.sphero_class.collision_detection_callback is not None: + self.notificationPacket = [] # need to clear packet, in case new notification comes in during callback + self.sphero_class.collision_detection_callback() + + # Sensor response: + elif devid == deviceID['sensor'] and commcode == sensorCommands['sensorResponse']: + # Convert to binary, pad bytes with leading zeros: + val = '' + for byte in notification_payload: + val += format(int(bin(byte)[2:], 2), '#010b')[2:] + + # Break into 32-bit chunks + nums = [] + while(len(val) > 0): + num, val = val[:32], val[32:] # Slice off first 16 bits + nums.append(num) + + # convert from raw bits to float: + nums = [self.bits_to_num(num) for num in nums] + + # Set sensor values as class attributes: + for name, value in zip(self.sphero_class.configured_sensors, nums): + print("Setting sensor at .", name, str(value)) + setattr(self.sphero_class, name, value) + + # Unrecognized packet structure: + else: + self.notification_ack = "Unknown asynchronous notification" #print(self.notificationPacket) + print(str(self.notificationPacket) + " ===================> Unknown async packet") + + self.notificationPacket = [] # Start new payload after this byte \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefront.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefront.py new file mode 100644 index 0000000..1da3877 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefront.py @@ -0,0 +1,154 @@ +#!/usr/bin/env python +import time +import sys + +def newSearch(mapOfWorld, goal, start): + heap = [] + newheap = [] + x, y = goal + lastwave = 3 + # Start out by marking nodes around G with a 3 + moves = [(x + 1, y), (x - 1, y), (x, y - 1), (x, y + 1)] + + for move in moves: + if(mapOfWorld.positions[move] == ' '): + mapOfWorld.positions[move] = 3 + heap.append(move) + for currentwave in range(4, 10000): + lastwave = lastwave + 1 + while(heap != []): + position = heap.pop() + (x, y) = position + moves = [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)] + #x, y = position + for move in moves: + if(mapOfWorld.positions[move] != 'W'): + if(mapOfWorld.positions[move] == ' ' and mapOfWorld.positions[position] == currentwave - 1): + mapOfWorld.positions[move] = currentwave + newheap.append(move) + if(move == start): + return mapOfWorld, lastwave + + #time.sleep(0.25) + #mapOfWorld.display() + #print heap + if(newheap == []): + print("Goal is unreachable") + return 1 + heap = newheap + newheap = [] + +def printf(format, *args): + sys.stdout.write(format % args) + +class Map(object): + + def __init__(self, xdim, ydim, positions): + self.xdim = xdim + self.ydim = ydim + self.positions = positions + def display(self): + printf(" ") + for i in range(self.ydim): + printf("%3s", str(i)) + print + for x in range(self.xdim): + printf("%2s", str(x)) + for y in range(self.ydim): + printf("%3s", str(self.positions[(x, y)])) + print + # Navigate though the number-populated maze + def nav(self, start, current): + self.pos = start + finished = False + + while(finished == False): # Run this code until we're at the goal + x, y = self.pos + self.positions[self.pos] = 'R' # Set the start on the map (this USUALLY keeps start the same) + # SOUTH NORTH WEST EAST + # v v v v + moves = [(x + 1, y), (x - 1, y), (x, y - 1), (x, y + 1)] # Establish our directions + moveDirections = ["South", "North", "West", "East"] # Create a corresponding list of the cardinal directions + """ We don't want least to be 0, because then nothing would be less than it. + However, in order to make our code more robust, we set it to one of the values, + so that we're comparing least to an actual value instead of an arbitrary number (like 10). + """ + # Do the actual comparing, and give us the least index so we know which move was the least + for w in range(len(moves)): + move = moves[w] + + # If the position has the current wave - 1 in it, move there. + if(self.positions[move] == current - 1): + self.least = self.positions[move] + leastIndex = w + # Or, if the position is the goal, stop the loop + elif(self.positions[move] == 'G'): + finished = True + leastIndex = w + # Decrement the current number so we can look for the next number + current = current - 1 + self.positions[self.pos] = ' ' + print( "Moved " + moveDirections[leastIndex]) + self.pos = moves[leastIndex] # This will be converted to "move robot in x direction" + + #time.sleep(0.25) + #self.display() + # Change the goal position (or wherever we stop) to an "!" to show that we've arrived. + self.positions[self.pos] = '!' + self.display() +# Find the goal, given the map +def findGoal(mapOfWorld): + positions = mapOfWorld.positions + for x in range(mapOfWorld.xdim): + for y in range(mapOfWorld.ydim): + if(mapOfWorld.positions[(x, y)] == 'G'): + return (x, y) +# Find the start, given the map +def findStart(mapOfWorld): + positions = mapOfWorld.positions + for x in range(mapOfWorld.xdim): + for y in range(mapOfWorld.ydim): + if(mapOfWorld.positions[(x, y)] == 'R'): + + return (x, y) + +def convertMap(mapOfWorld): + positions = {} + xdim = len(mapOfWorld) + ydim = len(mapOfWorld[1]) + for y in range(ydim): + for x in range(xdim): + positions[(x, y)] = mapOfWorld[x][y] + + return Map(xdim, ydim, positions) + +# Map erstellen +# Standard imports +import cv2 +import numpy as np; + +# Img Objekt +cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + +success, img = cap.read() + +gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY) +mask = cv2.inRange(gray, np.array([90]), np.array([255])) + +mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + +mask_list= np.ndarray.tolist(mask) + +for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['W' if j > 200 else ' ' for j in mask_list[i]] + +mapOfWorld[200][200] = 'R' #Start Pos +mapOfWorld[300][300] = 'G' #Ziel Pos + +print(mapOfWorld[200][200]) +print(mapOfWorld[300][300]) + +mapOfLand = convertMap(mapOfWorld) +mapOfLand.display() +mapOfLand, lastwave = newSearch(mapOfLand, findGoal(mapOfLand), findStart(mapOfLand)) +mapOfLand.nav(findStart(mapOfLand), lastwave) diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefront_coverage_path_planner.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefront_coverage_path_planner.py new file mode 100644 index 0000000..8586140 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefront_coverage_path_planner.py @@ -0,0 +1,218 @@ +""" +Distance/Path Transform Wavefront Coverage Path Planner + +author: Todd Tang +paper: Planning paths of complete coverage of an unstructured environment + by a mobile robot - Zelinsky et.al. +link: http://pinkwink.kr/attachment/cfile3.uf@1354654A4E8945BD13FE77.pdf +""" + +import os +import sys + +import matplotlib.pyplot as plt +import numpy as np +from scipy import ndimage + +do_animation = True + + +def transform( + grid_map, src, distance_type='chessboard', + transform_type='path', alpha=0.01 +): + """transform + + calculating transform of transform_type from src + in given distance_type + + :param grid_map: 2d binary map + :param src: distance transform source + :param distance_type: type of distance used + :param transform_type: type of transform used + :param alpha: weight of Obstacle Transform used when using path_transform + """ + + n_rows, n_cols = grid_map.shape + + if n_rows == 0 or n_cols == 0: + sys.exit('Empty grid_map.') + + inc_order = [[0, 1], [1, 1], [1, 0], [1, -1], + [0, -1], [-1, -1], [-1, 0], [-1, 1]] + if distance_type == 'chessboard': + cost = [1, 1, 1, 1, 1, 1, 1, 1] + elif distance_type == 'eculidean': + cost = [1, np.sqrt(2), 1, np.sqrt(2), 1, np.sqrt(2), 1, np.sqrt(2)] + else: + sys.exit('Unsupported distance type.') + + transform_matrix = float('inf') * np.ones_like(grid_map, dtype=float) + transform_matrix[src[0], src[1]] = 0 + if transform_type == 'distance': + eT = np.zeros_like(grid_map) + elif transform_type == 'path': + eT = ndimage.distance_transform_cdt(1 - grid_map, distance_type) + else: + sys.exit('Unsupported transform type.') + + # set obstacle transform_matrix value to infinity + for i in range(n_rows): + for j in range(n_cols): + if grid_map[i][j] == 1.0: + transform_matrix[i][j] = float('inf') + is_visited = np.zeros_like(transform_matrix, dtype=bool) + is_visited[src[0], src[1]] = True + traversal_queue = [src] + calculated = [(src[0] - 1) * n_cols + src[1]] + + def is_valid_neighbor(g_i, g_j): + return 0 <= g_i < n_rows and 0 <= g_j < n_cols \ + and not grid_map[g_i][g_j] + + while traversal_queue: + i, j = traversal_queue.pop(0) + for k, inc in enumerate(inc_order): + ni = i + inc[0] + nj = j + inc[1] + if is_valid_neighbor(ni, nj): + is_visited[i][j] = True + + # update transform_matrix + transform_matrix[i][j] = min( + transform_matrix[i][j], + transform_matrix[ni][nj] + cost[k] + alpha * eT[ni][nj]) + + if not is_visited[ni][nj] \ + and ((ni - 1) * n_cols + nj) not in calculated: + traversal_queue.append((ni, nj)) + calculated.append((ni - 1) * n_cols + nj) + + return transform_matrix + + +def get_search_order_increment(start, goal): + if start[0] >= goal[0] and start[1] >= goal[1]: + order = [[1, 0], [0, 1], [-1, 0], [0, -1], + [1, 1], [1, -1], [-1, 1], [-1, -1]] + elif start[0] <= goal[0] and start[1] >= goal[1]: + order = [[-1, 0], [0, 1], [1, 0], [0, -1], + [-1, 1], [-1, -1], [1, 1], [1, -1]] + elif start[0] >= goal[0] and start[1] <= goal[1]: + order = [[1, 0], [0, -1], [-1, 0], [0, 1], + [1, -1], [-1, -1], [1, 1], [-1, 1]] + elif start[0] <= goal[0] and start[1] <= goal[1]: + order = [[-1, 0], [0, -1], [0, 1], [1, 0], + [-1, -1], [-1, 1], [1, -1], [1, 1]] + else: + sys.exit('get_search_order_increment: cannot determine \ + start=>goal increment order') + return order + + +def wavefront(transform_matrix, start, goal): + """wavefront + + performing wavefront coverage path planning + + :param transform_matrix: the transform matrix + :param start: start point of planning + :param goal: goal point of planning + """ + + path = [] + n_rows, n_cols = transform_matrix.shape + + def is_valid_neighbor(g_i, g_j): + is_i_valid_bounded = 0 <= g_i < n_rows + is_j_valid_bounded = 0 <= g_j < n_cols + if is_i_valid_bounded and is_j_valid_bounded: + return not is_visited[g_i][g_j] and \ + transform_matrix[g_i][g_j] != float('inf') + return False + + inc_order = get_search_order_increment(start, goal) + + current_node = start + is_visited = np.zeros_like(transform_matrix, dtype=bool) + + while current_node != goal: + i, j = current_node + path.append((i, j)) + is_visited[i][j] = True + + max_T = float('-inf') + i_max = (-1, -1) + i_last = 0 + for i_last in range(len(path)): + current_node = path[-1 - i_last] # get latest node in path + for ci, cj in inc_order: + ni, nj = current_node[0] + ci, current_node[1] + cj + if is_valid_neighbor(ni, nj) and \ + transform_matrix[ni][nj] > max_T: + i_max = (ni, nj) + max_T = transform_matrix[ni][nj] + + if i_max != (-1, -1): + break + + if i_max == (-1, -1): + break + else: + current_node = i_max + if i_last != 0: + print('backtracing to', current_node) + path.append(goal) + + return path + + +def visualize_path(grid_map, start, goal, path): # pragma: no cover + oy, ox = start + gy, gx = goal + px, py = np.transpose(np.flipud(np.fliplr(path))) + + if not do_animation: + plt.imshow(grid_map, cmap='Greys') + plt.plot(ox, oy, "-xy") + plt.plot(px, py, "-r") + plt.plot(gx, gy, "-pg") + plt.show() + else: + for ipx, ipy in zip(px, py): + plt.cla() + # for stopping simulation with the esc key. + plt.gcf().canvas.mpl_connect( + 'key_release_event', + lambda event: [exit(0) if event.key == 'escape' else None]) + plt.imshow(grid_map, cmap='Greys') + plt.plot(ox, oy, "-xb") + plt.plot(px, py, "-r") + plt.plot(gx, gy, "-pg") + plt.plot(ipx, ipy, "or") + plt.axis("equal") + plt.grid(True) + plt.pause(0.1) + + +def main(): + dir_path = os.path.dirname(os.path.realpath(__file__)) + img = plt.imread(os.path.join(dir_path, 'map', 'test_2.png')) + img = 1 - img # revert pixel values + + start = (43, 0) + goal = (0, 0) + + # distance transform wavefront + DT = transform(img, goal, transform_type='distance') + DT_path = wavefront(DT, start, goal) + visualize_path(img, start, goal, DT_path) + + # path transform wavefront + PT = transform(img, goal, transform_type='path', alpha=0.01) + PT_path = wavefront(PT, start, goal) + visualize_path(img, start, goal, PT_path) + + +if __name__ == "__main__": + main() diff --git a/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefrontgpt2.py b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefrontgpt2.py new file mode 100644 index 0000000..ef76c9f --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/wavefrontgpt2.py @@ -0,0 +1,76 @@ +from queue import Queue + +def wavefront_planner(map_array, start_pos, goal_pos): + rows = len(map_array) + cols = len(map_array[0]) + + # Überprüfe, ob Start- und Zielposition innerhalb der Karte liegen + if (start_pos[0] < 0 or start_pos[0] >= rows or start_pos[1] < 0 or start_pos[1] >= cols or + goal_pos[0] < 0 or goal_pos[0] >= rows or goal_pos[1] < 0 or goal_pos[1] >= cols): + raise ValueError("Start or goal position is out of bounds.") + + # Erzeuge eine Kopie der Karte für den Wavefront-Algorithmus + wavefront_map = [[-1] * cols for _ in range(rows)] + + # Definiere die Bewegungsrichtungen (4-Wege-Bewegung: oben, unten, links, rechts) + directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] + + # Erzeuge eine Warteschlange für die Wellenfrontausbreitung + queue = Queue() + queue.put(goal_pos) + + # Führe die Wellenfrontausbreitung durch + wavefront_map[goal_pos[0]][goal_pos[1]] = 0 + while not queue.empty(): + current_pos = queue.get() + + # Überprüfe die Nachbarzellen + for direction in directions: + new_pos = (current_pos[0] + direction[0], current_pos[1] + direction[1]) + + # Überprüfe, ob die Nachbarzelle gültig und noch nicht besucht ist + if (0 <= new_pos[0] < rows and 0 <= new_pos[1] < cols and + wavefront_map[new_pos[0]][new_pos[1]] == -1 and map_array[new_pos[0]][new_pos[1]] == 0): + wavefront_map[new_pos[0]][new_pos[1]] = wavefront_map[current_pos[0]][current_pos[1]] + 1 + queue.put(new_pos) + + # Überprüfe, ob der Startpunkt erreichbar ist + if wavefront_map[start_pos[0]][start_pos[1]] == -1: + raise ValueError("Start position is unreachable.") + + # Verfolge den Pfad basierend auf der Wellenfront + path = [start_pos] + current_pos = start_pos + while current_pos != goal_pos: + next_pos = None + min_distance = float('inf') + + for direction in directions: + neighbor_pos = (current_pos[0] + direction[0], current_pos[1] + direction[1]) + + if (0 <= neighbor_pos[0] < rows and 0 <= neighbor_pos[1] < cols and + wavefront_map[neighbor_pos[0]][neighbor_pos[1]] < min_distance): + next_pos = neighbor_pos + min_distance = wavefront_map[neighbor_pos[0]][neighbor_pos[1]] + + if next_pos is None: + raise ValueError("No path found.") + + path.append(next_pos) + current_pos = next_pos + + return path + +# Beispielverwendung +map_array = [ + [0, 0, 0, 0], + [0, 1, 1, 0], + [0, 0, 0, 0], + [0, 1, 1, 0], +] + +start_pos = (0, 0) +goal_pos = (1, 3) + +path = wavefront_planner(map_array, start_pos, goal_pos) +print("Path:", path) diff --git a/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller/sphero_mini b/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller/sphero_mini new file mode 100644 index 0000000..01ae998 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller/sphero_mini @@ -0,0 +1,33 @@ +#!/usr/bin/python3 +# EASY-INSTALL-ENTRY-SCRIPT: 'sphero-mini-controller==0.0.0','console_scripts','sphero_mini' +import re +import sys + +# for compatibility with easy_install; see #2198 +__requires__ = 'sphero-mini-controller==0.0.0' + +try: + from importlib.metadata import distribution +except ImportError: + try: + from importlib_metadata import distribution + except ImportError: + from pkg_resources import load_entry_point + + +def importlib_load_entry_point(spec, group, name): + dist_name, _, _ = spec.partition('==') + matches = ( + entry_point + for entry_point in distribution(dist_name).entry_points + if entry_point.group == group and entry_point.name == name + ) + return next(matches).load() + + +globals().setdefault('load_entry_point', importlib_load_entry_point) + + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(load_entry_point('sphero-mini-controller==0.0.0', 'console_scripts', 'sphero_mini')()) diff --git a/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller/test_node b/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller/test_node new file mode 100644 index 0000000..42b5555 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller/test_node @@ -0,0 +1,33 @@ +#!/usr/bin/python3 +# EASY-INSTALL-ENTRY-SCRIPT: 'sphero-mini-controller==0.0.0','console_scripts','test_node' +import re +import sys + +# for compatibility with easy_install; see #2198 +__requires__ = 'sphero-mini-controller==0.0.0' + +try: + from importlib.metadata import distribution +except ImportError: + try: + from importlib_metadata import distribution + except ImportError: + from pkg_resources import load_entry_point + + +def importlib_load_entry_point(spec, group, name): + dist_name, _, _ = spec.partition('==') + matches = ( + entry_point + for entry_point in distribution(dist_name).entry_points + if entry_point.group == group and entry_point.name == name + ) + return next(matches).load() + + +globals().setdefault('load_entry_point', importlib_load_entry_point) + + +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]) + sys.exit(load_entry_point('sphero-mini-controller==0.0.0', 'console_scripts', 'test_node')()) diff --git a/ros2_ws/install/sphero_mini_controller/share/ament_index/resource_index/packages/sphero_mini_controller b/ros2_ws/install/sphero_mini_controller/share/ament_index/resource_index/packages/sphero_mini_controller new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/install/sphero_mini_controller/share/colcon-core/packages/sphero_mini_controller b/ros2_ws/install/sphero_mini_controller/share/colcon-core/packages/sphero_mini_controller new file mode 100644 index 0000000..4fb1cbf --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/colcon-core/packages/sphero_mini_controller @@ -0,0 +1 @@ +_constants:bluepy:bluepy.btle:box:cv2:geometry_msgs.msg:matplotlib.pyplot:numpy:queue:rclpy:sensor_msgs.msg:struct:sys:threading:time:treiber \ No newline at end of file diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.dsv b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.dsv new file mode 100644 index 0000000..79d4c95 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;AMENT_PREFIX_PATH; diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.ps1 b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.ps1 new file mode 100644 index 0000000..26b9997 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value AMENT_PREFIX_PATH "$env:COLCON_CURRENT_PREFIX" diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.sh b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.sh new file mode 100644 index 0000000..f3041f6 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value AMENT_PREFIX_PATH "$COLCON_CURRENT_PREFIX" diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.dsv b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.dsv new file mode 100644 index 0000000..257067d --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.dsv @@ -0,0 +1 @@ +prepend-non-duplicate;PYTHONPATH;lib/python3.10/site-packages diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.ps1 b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.ps1 new file mode 100644 index 0000000..caffe83 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.ps1 @@ -0,0 +1,3 @@ +# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em + +colcon_prepend_unique_value PYTHONPATH "$env:COLCON_CURRENT_PREFIX\lib/python3.10/site-packages" diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.sh b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.sh new file mode 100644 index 0000000..660c348 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.sh @@ -0,0 +1,3 @@ +# generated from colcon_core/shell/template/hook_prepend_value.sh.em + +_colcon_prepend_unique_value PYTHONPATH "$COLCON_CURRENT_PREFIX/lib/python3.10/site-packages" diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.bash b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.bash new file mode 100644 index 0000000..ba62e9b --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.bash @@ -0,0 +1,31 @@ +# generated from colcon_bash/shell/template/package.bash.em + +# This script extends the environment for this package. + +# a bash script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" +else + _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_bash_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh script of this package +_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/sphero_mini_controller/package.sh" + +unset _colcon_package_bash_source_script +unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.dsv b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.dsv new file mode 100644 index 0000000..2e5c1c2 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.dsv @@ -0,0 +1,6 @@ +source;share/sphero_mini_controller/hook/pythonpath.ps1 +source;share/sphero_mini_controller/hook/pythonpath.dsv +source;share/sphero_mini_controller/hook/pythonpath.sh +source;share/sphero_mini_controller/hook/ament_prefix_path.ps1 +source;share/sphero_mini_controller/hook/ament_prefix_path.dsv +source;share/sphero_mini_controller/hook/ament_prefix_path.sh diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.ps1 b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.ps1 new file mode 100644 index 0000000..66a433e --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.ps1 @@ -0,0 +1,116 @@ +# generated from colcon_powershell/shell/template/package.ps1.em + +# function to append a value to a variable +# which uses colons as separators +# duplicates as well as leading separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_append_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + $_duplicate="" + # start with no values + $_all_values="" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -eq $_value) { + $_duplicate="1" + } + if ($_all_values) { + $_all_values="${_all_values};$_" + } else { + $_all_values="$_" + } + } + } + } + # append only non-duplicates + if (!$_duplicate) { + # avoid leading separator + if ($_all_values) { + $_all_values="${_all_values};${_value}" + } else { + $_all_values="${_value}" + } + } + + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +function colcon_prepend_unique_value { + param ( + $_listname, + $_value + ) + + # get values from variable + if (Test-Path Env:$_listname) { + $_values=(Get-Item env:$_listname).Value + } else { + $_values="" + } + # start with the new value + $_all_values="$_value" + # iterate over existing values in the variable + if ($_values) { + $_values.Split(";") | ForEach { + # not an empty string + if ($_) { + # not a duplicate of _value + if ($_ -ne $_value) { + # keep non-duplicate values + $_all_values="${_all_values};$_" + } + } + } + } + # export the updated variable + Set-Item env:\$_listname -Value "$_all_values" +} + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +function colcon_package_source_powershell_script { + param ( + $_colcon_package_source_powershell_script + ) + # source script with conditional trace output + if (Test-Path $_colcon_package_source_powershell_script) { + if ($env:COLCON_TRACE) { + echo ". '$_colcon_package_source_powershell_script'" + } + . "$_colcon_package_source_powershell_script" + } else { + Write-Error "not found: '$_colcon_package_source_powershell_script'" + } +} + + +# a powershell script is able to determine its own path +# the prefix is two levels up from the package specific share directory +$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName + +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/sphero_mini_controller/hook/pythonpath.ps1" +colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/sphero_mini_controller/hook/ament_prefix_path.ps1" + +Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.sh b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.sh new file mode 100644 index 0000000..ccf55cf --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.sh @@ -0,0 +1,87 @@ +# generated from colcon_core/shell/template/package.sh.em + +# This script extends the environment for this package. + +# function to prepend a value to a variable +# which uses colons as separators +# duplicates as well as trailing separators are avoided +# first argument: the name of the result variable +# second argument: the value to be prepended +_colcon_prepend_unique_value() { + # arguments + _listname="$1" + _value="$2" + + # get values from variable + eval _values=\"\$$_listname\" + # backup the field separator + _colcon_prepend_unique_value_IFS=$IFS + IFS=":" + # start with the new value + _all_values="$_value" + # workaround SH_WORD_SPLIT not being set in zsh + if [ "$(command -v colcon_zsh_convert_to_array)" ]; then + colcon_zsh_convert_to_array _values + fi + # iterate over existing values in the variable + for _item in $_values; do + # ignore empty strings + if [ -z "$_item" ]; then + continue + fi + # ignore duplicates of _value + if [ "$_item" = "$_value" ]; then + continue + fi + # keep non-duplicate values + _all_values="$_all_values:$_item" + done + unset _item + # restore the field separator + IFS=$_colcon_prepend_unique_value_IFS + unset _colcon_prepend_unique_value_IFS + # export the updated variable + eval export $_listname=\"$_all_values\" + unset _all_values + unset _values + + unset _value + unset _listname +} + +# since a plain shell script can't determine its own path when being sourced +# either use the provided COLCON_CURRENT_PREFIX +# or fall back to the build time prefix (if it exists) +_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/ubuntu/ros2_ws/install/sphero_mini_controller" +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then + echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 + unset _colcon_package_sh_COLCON_CURRENT_PREFIX + return 1 + fi + COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" +fi +unset _colcon_package_sh_COLCON_CURRENT_PREFIX + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_sh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo "# . \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# source sh hooks +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/sphero_mini_controller/hook/pythonpath.sh" +_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/sphero_mini_controller/hook/ament_prefix_path.sh" + +unset _colcon_package_sh_source_script +unset COLCON_CURRENT_PREFIX + +# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.xml b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.xml new file mode 100644 index 0000000..abe0e77 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.xml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> +<package format="3"> + <name>sphero_mini_controller</name> + <version>0.0.0</version> + <description>TODO: Package description</description> + <maintainer email="ubuntu@todo.todo">ubuntu</maintainer> + <license>TODO: License declaration</license> + + <depend>rclpy</depend> + <depend>sensor_msgs.msg</depend> + <depend>geometry_msgs.msg</depend> + <depend>box</depend> + <depend>numpy</depend> + <depend>treiber</depend> + <depend>bluepy.btle</depend> + <depend>bluepy</depend> + <depend>_constants</depend> + <depend>struct</depend> + <depend>time</depend> + <depend>sys</depend> + <depend>threading</depend> + <depend>cv2</depend> + <depend>queue</depend> + <depend>matplotlib.pyplot</depend> + + <test_depend>ament_copyright</test_depend> + <test_depend>ament_flake8</test_depend> + <test_depend>ament_pep257</test_depend> + <test_depend>python3-pytest</test_depend> + + <export> + <build_type>ament_python</build_type> + </export> +</package> diff --git a/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.zsh b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.zsh new file mode 100644 index 0000000..4229ad7 --- /dev/null +++ b/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.zsh @@ -0,0 +1,42 @@ +# generated from colcon_zsh/shell/template/package.zsh.em + +# This script extends the environment for this package. + +# a zsh script is able to determine its own path if necessary +if [ -z "$COLCON_CURRENT_PREFIX" ]; then + # the prefix is two levels up from the package specific share directory + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" +else + _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" +fi + +# function to source another script with conditional trace output +# first argument: the path of the script +# additional arguments: arguments to the script +_colcon_package_zsh_source_script() { + if [ -f "$1" ]; then + if [ -n "$COLCON_TRACE" ]; then + echo ". \"$1\"" + fi + . "$@" + else + echo "not found: \"$1\"" 1>&2 + fi +} + +# function to convert array-like strings into arrays +# to workaround SH_WORD_SPLIT not being set +colcon_zsh_convert_to_array() { + local _listname=$1 + local _dollar="$" + local _split="{=" + local _to_array="(\"$_dollar$_split$_listname}\")" + eval $_listname=$_to_array +} + +# source sh script of this package +_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/sphero_mini_controller/package.sh" +unset convert_zsh_to_array + +unset _colcon_package_zsh_source_script +unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/ros2_ws/log/COLCON_IGNORE b/ros2_ws/log/COLCON_IGNORE new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/log/build_2023-07-05_16-11-36/events.log b/ros2_ws/log/build_2023-07-05_16-11-36/events.log new file mode 100644 index 0000000..b51af15 --- /dev/null +++ b/ros2_ws/log/build_2023-07-05_16-11-36/events.log @@ -0,0 +1,38 @@ +[0.000000] (-) TimerEvent: {} +[0.000094] (sphero_mini_controller) JobQueued: {'identifier': 'sphero_mini_controller', 'dependencies': OrderedDict()} +[0.000145] (sphero_mini_controller) JobStarted: {'identifier': 'sphero_mini_controller'} +[0.098612] (-) TimerEvent: {} +[0.198900] (-) TimerEvent: {} +[0.299181] (-) TimerEvent: {} +[0.399461] (-) TimerEvent: {} +[0.499733] (-) TimerEvent: {} +[0.600065] (-) TimerEvent: {} +[0.613340] (sphero_mini_controller) Command: {'cmd': ['/usr/bin/python3', 'setup.py', 'egg_info', '--egg-base', '../../build/sphero_mini_controller', 'build', '--build-base', '/home/ubuntu/ros2_ws/build/sphero_mini_controller/build', 'install', '--record', '/home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log', '--single-version-externally-managed'], 'cwd': '/home/ubuntu/ros2_ws/src/sphero_mini_controller', 'env': {'LANGUAGE': 'de_DE:en', 'USER': 'ubuntu', 'LC_TIME': 'de_DE.UTF-8', 'XDG_SESSION_TYPE': 'x11', 'SHLVL': '1', 'LD_LIBRARY_PATH': '/opt/ros/humble/opt/rviz_ogre_vendor/lib:/opt/ros/humble/lib/x86_64-linux-gnu:/opt/ros/humble/lib', 'HOME': '/home/ubuntu', 'OLDPWD': '/home/ubuntu', 'DESKTOP_SESSION': 'ubuntu', 'ROS_PYTHON_VERSION': '3', 'GNOME_SHELL_SESSION_MODE': 'ubuntu', 'GTK_MODULES': 'gail:atk-bridge', 'LC_MONETARY': 'de_DE.UTF-8', 'SYSTEMD_EXEC_PID': '2511', 'DBUS_SESSION_BUS_ADDRESS': 'unix:path=/run/user/999/bus', 'COLORTERM': 'truecolor', 'IM_CONFIG_PHASE': '1', 'ROS_DISTRO': 'humble', 'LOGNAME': 'ubuntu', '_': '/usr/bin/colcon', 'ROS_VERSION': '2', 'XDG_SESSION_CLASS': 'user', 'USERNAME': 'ubuntu', 'TERM': 'xterm-256color', 'GNOME_DESKTOP_SESSION_ID': 'this-is-deprecated', 'ROS_LOCALHOST_ONLY': '0', 'WINDOWPATH': '2', 'PATH': '/opt/ros/humble/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin', 'SESSION_MANAGER': 'local/ubuntu:@/tmp/.ICE-unix/2486,unix/ubuntu:/tmp/.ICE-unix/2486', 'PAPERSIZE': 'a4', 'XDG_MENU_PREFIX': 'gnome-', 'LC_ADDRESS': 'de_DE.UTF-8', 'GNOME_TERMINAL_SCREEN': '/org/gnome/Terminal/screen/e134bf1a_3465_43a9_88f4_21d27316a303', 'XDG_RUNTIME_DIR': '/run/user/999', 'DISPLAY': ':0', 'LANG': 'de_DE.UTF-8', 'XDG_CURRENT_DESKTOP': 'ubuntu:GNOME', 'LC_TELEPHONE': 'de_DE.UTF-8', 'XMODIFIERS': '@im=ibus', 'XDG_SESSION_DESKTOP': 'ubuntu', 'XAUTHORITY': '/run/user/999/gdm/Xauthority', 'GNOME_TERMINAL_SERVICE': ':1.129', 'SSH_AGENT_LAUNCHER': 'gnome-keyring', 'SSH_AUTH_SOCK': '/run/user/999/keyring/ssh', 'AMENT_PREFIX_PATH': '/opt/ros/humble', 'SHELL': '/bin/bash', 'LC_NAME': 'de_DE.UTF-8', 'QT_ACCESSIBILITY': '1', 'GDMSESSION': 'ubuntu', 'WEBOTS_HOME': '/usr/local/webots', 'LC_MEASUREMENT': 'de_DE.UTF-8', 'GPG_AGENT_INFO': '/run/user/999/gnupg/S.gpg-agent:0:1', 'LC_IDENTIFICATION': 'de_DE.UTF-8', 'QT_IM_MODULE': 'ibus', 'PWD': '/home/ubuntu/ros2_ws/build/sphero_mini_controller', 'XDG_CONFIG_DIRS': '/etc/xdg/xdg-ubuntu:/etc/xdg', 'XDG_DATA_DIRS': '/usr/share/ubuntu:/usr/share/gnome:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop', 'PYTHONPATH': '/home/ubuntu/ros2_ws/build/sphero_mini_controller/prefix_override:/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages:/opt/ros/humble/lib/python3.10/site-packages:/opt/ros/humble/local/lib/python3.10/dist-packages', 'LC_NUMERIC': 'de_DE.UTF-8', 'LC_PAPER': 'de_DE.UTF-8', 'COLCON': '1', 'VTE_VERSION': '6800'}, 'shell': False} +[0.700191] (-) TimerEvent: {} +[0.800481] (-) TimerEvent: {} +[0.837582] (sphero_mini_controller) StdoutLine: {'line': b'running egg_info\n'} +[0.838134] (sphero_mini_controller) StdoutLine: {'line': b'writing ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO\n'} +[0.838269] (sphero_mini_controller) StdoutLine: {'line': b'writing dependency_links to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt\n'} +[0.838506] (sphero_mini_controller) StdoutLine: {'line': b'writing entry points to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt\n'} +[0.838674] (sphero_mini_controller) StdoutLine: {'line': b'writing requirements to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt\n'} +[0.838808] (sphero_mini_controller) StdoutLine: {'line': b'writing top-level names to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt\n'} +[0.840404] (sphero_mini_controller) StdoutLine: {'line': b"reading manifest file '../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt'\n"} +[0.841553] (sphero_mini_controller) StdoutLine: {'line': b"writing manifest file '../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt'\n"} +[0.841722] (sphero_mini_controller) StdoutLine: {'line': b'running build\n'} +[0.841821] (sphero_mini_controller) StdoutLine: {'line': b'running build_py\n'} +[0.842619] (sphero_mini_controller) StdoutLine: {'line': b'copying sphero_mini_controller/my_first_node.py -> /home/ubuntu/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller\n'} +[0.843943] (sphero_mini_controller) StdoutLine: {'line': b'running install\n'} +[0.844204] (sphero_mini_controller) StdoutLine: {'line': b'running install_lib\n'} +[0.844828] (sphero_mini_controller) StdoutLine: {'line': b'copying /home/ubuntu/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node.py -> /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller\n'} +[0.846179] (sphero_mini_controller) StdoutLine: {'line': b'byte-compiling /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node.py to my_first_node.cpython-310.pyc\n'} +[0.850045] (sphero_mini_controller) StdoutLine: {'line': b'running install_data\n'} +[0.851406] (sphero_mini_controller) StdoutLine: {'line': b'running install_egg_info\n'} +[0.852604] (sphero_mini_controller) StdoutLine: {'line': b"removing '/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info' (and everything under it)\n"} +[0.852894] (sphero_mini_controller) StdoutLine: {'line': b'Copying ../../build/sphero_mini_controller/sphero_mini_controller.egg-info to /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info\n'} +[0.853897] (sphero_mini_controller) StdoutLine: {'line': b'running install_scripts\n'} +[0.869686] (sphero_mini_controller) StdoutLine: {'line': b'Installing sphero_mini script to /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller\n'} +[0.870533] (sphero_mini_controller) StdoutLine: {'line': b"writing list of installed files to '/home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log'\n"} +[0.894265] (sphero_mini_controller) CommandEnded: {'returncode': 0} +[0.900811] (-) TimerEvent: {} +[0.911226] (sphero_mini_controller) JobEnded: {'identifier': 'sphero_mini_controller', 'rc': 0} +[0.911779] (-) EventReactorShutdown: {} diff --git a/ros2_ws/log/build_2023-07-05_16-11-36/logger_all.log b/ros2_ws/log/build_2023-07-05_16-11-36/logger_all.log new file mode 100644 index 0000000..79ba79d --- /dev/null +++ b/ros2_ws/log/build_2023-07-05_16-11-36/logger_all.log @@ -0,0 +1,126 @@ +[0.285s] DEBUG:colcon:Command line arguments: ['/usr/bin/colcon', 'build'] +[0.285s] DEBUG:colcon:Parsed command line arguments: Namespace(log_base=None, log_level=None, verb_name='build', build_base='build', install_base='install', merge_install=False, symlink_install=False, test_result_base=None, continue_on_error=False, executor='parallel', parallel_workers=8, event_handlers=None, ignore_user_meta=False, metas=['./colcon.meta'], base_paths=['.'], packages_ignore=None, packages_ignore_regex=None, paths=None, packages_up_to=None, packages_up_to_regex=None, packages_above=None, packages_above_and_dependencies=None, packages_above_depth=None, packages_select_by_dep=None, packages_skip_by_dep=None, packages_skip_up_to=None, packages_select_build_failed=False, packages_skip_build_finished=False, packages_select_test_failures=False, packages_skip_test_passed=False, packages_select=None, packages_skip=None, packages_select_regex=None, packages_skip_regex=None, packages_start=None, packages_end=None, allow_overriding=[], cmake_args=None, cmake_target=None, cmake_target_skip_unavailable=False, cmake_clean_cache=False, cmake_clean_first=False, cmake_force_configure=False, ament_cmake_args=None, catkin_cmake_args=None, catkin_skip_building_tests=False, verb_parser=<colcon_defaults.argument_parser.defaults.DefaultArgumentsDecorator object at 0x7f34ebc57490>, verb_extension=<colcon_core.verb.build.BuildVerb object at 0x7f34ebc56f20>, main=<bound method BuildVerb.main of <colcon_core.verb.build.BuildVerb object at 0x7f34ebc56f20>>) +[0.322s] Level 1:colcon.colcon_core.package_discovery:discover_packages(colcon_meta) check parameters +[0.322s] Level 1:colcon.colcon_core.package_discovery:discover_packages(recursive) check parameters +[0.322s] Level 1:colcon.colcon_core.package_discovery:discover_packages(ignore) check parameters +[0.322s] Level 1:colcon.colcon_core.package_discovery:discover_packages(path) check parameters +[0.322s] Level 1:colcon.colcon_core.package_discovery:discover_packages(colcon_meta) discover +[0.322s] Level 1:colcon.colcon_core.package_discovery:discover_packages(recursive) discover +[0.322s] INFO:colcon.colcon_core.package_discovery:Crawling recursively for packages in '/home/ubuntu/ros2_ws' +[0.322s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extensions ['ignore', 'ignore_ament_install'] +[0.322s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extension 'ignore' +[0.322s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extension 'ignore_ament_install' +[0.322s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extensions ['colcon_pkg'] +[0.322s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extension 'colcon_pkg' +[0.322s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extensions ['colcon_meta'] +[0.322s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extension 'colcon_meta' +[0.323s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extensions ['ros'] +[0.323s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extension 'ros' +[0.332s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extensions ['cmake', 'python'] +[0.332s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extension 'cmake' +[0.332s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extension 'python' +[0.332s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extensions ['python_setup_py'] +[0.332s] Level 1:colcon.colcon_core.package_identification:_identify(.) by extension 'python_setup_py' +[0.332s] Level 1:colcon.colcon_core.package_identification:_identify(build) by extensions ['ignore', 'ignore_ament_install'] +[0.332s] Level 1:colcon.colcon_core.package_identification:_identify(build) by extension 'ignore' +[0.332s] Level 1:colcon.colcon_core.package_identification:_identify(build) ignored +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(install) by extensions ['ignore', 'ignore_ament_install'] +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(install) by extension 'ignore' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(install) ignored +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(log) by extensions ['ignore', 'ignore_ament_install'] +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(log) by extension 'ignore' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(log) ignored +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extensions ['ignore', 'ignore_ament_install'] +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extension 'ignore' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extension 'ignore_ament_install' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extensions ['colcon_pkg'] +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extension 'colcon_pkg' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extensions ['colcon_meta'] +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extension 'colcon_meta' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extensions ['ros'] +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extension 'ros' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extensions ['cmake', 'python'] +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extension 'cmake' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extension 'python' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extensions ['python_setup_py'] +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src) by extension 'python_setup_py' +[0.333s] Level 1:colcon.colcon_core.package_identification:_identify(src/sphero_mini_controller) by extensions ['ignore', 'ignore_ament_install'] +[0.334s] Level 1:colcon.colcon_core.package_identification:_identify(src/sphero_mini_controller) by extension 'ignore' +[0.334s] Level 1:colcon.colcon_core.package_identification:_identify(src/sphero_mini_controller) by extension 'ignore_ament_install' +[0.334s] Level 1:colcon.colcon_core.package_identification:_identify(src/sphero_mini_controller) by extensions ['colcon_pkg'] +[0.334s] Level 1:colcon.colcon_core.package_identification:_identify(src/sphero_mini_controller) by extension 'colcon_pkg' +[0.334s] Level 1:colcon.colcon_core.package_identification:_identify(src/sphero_mini_controller) by extensions ['colcon_meta'] +[0.334s] Level 1:colcon.colcon_core.package_identification:_identify(src/sphero_mini_controller) by extension 'colcon_meta' +[0.334s] Level 1:colcon.colcon_core.package_identification:_identify(src/sphero_mini_controller) by extensions ['ros'] +[0.334s] Level 1:colcon.colcon_core.package_identification:_identify(src/sphero_mini_controller) by extension 'ros' +[0.338s] DEBUG:colcon.colcon_core.package_identification:Package 'src/sphero_mini_controller' with type 'ros.ament_python' and name 'sphero_mini_controller' +[0.338s] Level 1:colcon.colcon_core.package_discovery:discover_packages(recursive) using defaults +[0.338s] Level 1:colcon.colcon_core.package_discovery:discover_packages(ignore) discover +[0.338s] Level 1:colcon.colcon_core.package_discovery:discover_packages(ignore) using defaults +[0.338s] Level 1:colcon.colcon_core.package_discovery:discover_packages(path) discover +[0.338s] Level 1:colcon.colcon_core.package_discovery:discover_packages(path) using defaults +[0.359s] Level 1:colcon.colcon_core.package_discovery:discover_packages(prefix_path) check parameters +[0.359s] Level 1:colcon.colcon_core.package_discovery:discover_packages(prefix_path) discover +[0.364s] DEBUG:colcon.colcon_installed_package_information.package_discovery:Found 302 installed packages in /opt/ros/humble +[0.365s] Level 1:colcon.colcon_core.package_discovery:discover_packages(prefix_path) using defaults +[0.418s] Level 5:colcon.colcon_core.verb:set package 'sphero_mini_controller' build argument 'cmake_args' from command line to 'None' +[0.418s] Level 5:colcon.colcon_core.verb:set package 'sphero_mini_controller' build argument 'cmake_target' from command line to 'None' +[0.418s] Level 5:colcon.colcon_core.verb:set package 'sphero_mini_controller' build argument 'cmake_target_skip_unavailable' from command line to 'False' +[0.418s] Level 5:colcon.colcon_core.verb:set package 'sphero_mini_controller' build argument 'cmake_clean_cache' from command line to 'False' +[0.418s] Level 5:colcon.colcon_core.verb:set package 'sphero_mini_controller' build argument 'cmake_clean_first' from command line to 'False' +[0.418s] Level 5:colcon.colcon_core.verb:set package 'sphero_mini_controller' build argument 'cmake_force_configure' from command line to 'False' +[0.418s] Level 5:colcon.colcon_core.verb:set package 'sphero_mini_controller' build argument 'ament_cmake_args' from command line to 'None' +[0.418s] Level 5:colcon.colcon_core.verb:set package 'sphero_mini_controller' build argument 'catkin_cmake_args' from command line to 'None' +[0.418s] Level 5:colcon.colcon_core.verb:set package 'sphero_mini_controller' build argument 'catkin_skip_building_tests' from command line to 'False' +[0.418s] DEBUG:colcon.colcon_core.verb:Building package 'sphero_mini_controller' with the following arguments: {'ament_cmake_args': None, 'build_base': '/home/ubuntu/ros2_ws/build/sphero_mini_controller', 'catkin_cmake_args': None, 'catkin_skip_building_tests': False, 'cmake_args': None, 'cmake_clean_cache': False, 'cmake_clean_first': False, 'cmake_force_configure': False, 'cmake_target': None, 'cmake_target_skip_unavailable': False, 'install_base': '/home/ubuntu/ros2_ws/install/sphero_mini_controller', 'merge_install': False, 'path': '/home/ubuntu/ros2_ws/src/sphero_mini_controller', 'symlink_install': False, 'test_result_base': None} +[0.419s] INFO:colcon.colcon_core.executor:Executing jobs using 'parallel' executor +[0.421s] DEBUG:colcon.colcon_parallel_executor.executor.parallel:run_until_complete +[0.421s] INFO:colcon.colcon_ros.task.ament_python.build:Building ROS package in '/home/ubuntu/ros2_ws/src/sphero_mini_controller' with build type 'ament_python' +[0.421s] Level 1:colcon.colcon_core.shell:create_environment_hook('sphero_mini_controller', 'ament_prefix_path') +[0.427s] INFO:colcon.colcon_core.plugin_system:Skipping extension 'colcon_core.shell.bat': Not used on non-Windows systems +[0.427s] INFO:colcon.colcon_core.shell:Creating environment hook '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.ps1' +[0.428s] INFO:colcon.colcon_core.shell:Creating environment descriptor '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.dsv' +[0.429s] INFO:colcon.colcon_core.shell:Creating environment hook '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/ament_prefix_path.sh' +[0.431s] INFO:colcon.colcon_core.shell:Skip shell extension 'powershell' for command environment: Not usable outside of PowerShell +[0.431s] DEBUG:colcon.colcon_core.shell:Skip shell extension 'dsv' for command environment +[0.671s] INFO:colcon.colcon_core.task.python.build:Building Python package in '/home/ubuntu/ros2_ws/src/sphero_mini_controller' +[0.672s] INFO:colcon.colcon_core.shell:Skip shell extension 'powershell' for command environment: Not usable outside of PowerShell +[0.672s] DEBUG:colcon.colcon_core.shell:Skip shell extension 'dsv' for command environment +[1.036s] DEBUG:colcon.colcon_core.event_handler.log_command:Invoking command in '/home/ubuntu/ros2_ws/src/sphero_mini_controller': PYTHONPATH=/home/ubuntu/ros2_ws/build/sphero_mini_controller/prefix_override:/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages:${PYTHONPATH} /usr/bin/python3 setup.py egg_info --egg-base ../../build/sphero_mini_controller build --build-base /home/ubuntu/ros2_ws/build/sphero_mini_controller/build install --record /home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log --single-version-externally-managed +[1.317s] DEBUG:colcon.colcon_core.event_handler.log_command:Invoked command in '/home/ubuntu/ros2_ws/src/sphero_mini_controller' returned '0': PYTHONPATH=/home/ubuntu/ros2_ws/build/sphero_mini_controller/prefix_override:/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages:${PYTHONPATH} /usr/bin/python3 setup.py egg_info --egg-base ../../build/sphero_mini_controller build --build-base /home/ubuntu/ros2_ws/build/sphero_mini_controller/build install --record /home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log --single-version-externally-managed +[1.323s] Level 1:colcon.colcon_core.environment:checking '/home/ubuntu/ros2_ws/install/sphero_mini_controller' for CMake module files +[1.324s] Level 1:colcon.colcon_core.environment:checking '/home/ubuntu/ros2_ws/install/sphero_mini_controller' for CMake config files +[1.325s] Level 1:colcon.colcon_core.environment:checking '/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib' +[1.325s] Level 1:colcon.colcon_core.environment:checking '/home/ubuntu/ros2_ws/install/sphero_mini_controller/bin' +[1.325s] Level 1:colcon.colcon_core.environment:checking '/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/pkgconfig/sphero_mini_controller.pc' +[1.325s] Level 1:colcon.colcon_core.environment:checking '/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages' +[1.325s] Level 1:colcon.colcon_core.shell:create_environment_hook('sphero_mini_controller', 'pythonpath') +[1.327s] INFO:colcon.colcon_core.shell:Creating environment hook '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.ps1' +[1.327s] INFO:colcon.colcon_core.shell:Creating environment descriptor '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.dsv' +[1.327s] INFO:colcon.colcon_core.shell:Creating environment hook '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/hook/pythonpath.sh' +[1.328s] Level 1:colcon.colcon_core.environment:checking '/home/ubuntu/ros2_ws/install/sphero_mini_controller/bin' +[1.328s] Level 1:colcon.colcon_core.environment:create_environment_scripts_only(sphero_mini_controller) +[1.329s] INFO:colcon.colcon_core.shell:Creating package script '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.ps1' +[1.330s] INFO:colcon.colcon_core.shell:Creating package descriptor '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.dsv' +[1.331s] INFO:colcon.colcon_core.shell:Creating package script '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.sh' +[1.331s] INFO:colcon.colcon_core.shell:Creating package script '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.bash' +[1.332s] INFO:colcon.colcon_core.shell:Creating package script '/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/sphero_mini_controller/package.zsh' +[1.333s] Level 1:colcon.colcon_core.environment:create_file_with_runtime_dependencies(/home/ubuntu/ros2_ws/install/sphero_mini_controller/share/colcon-core/packages/sphero_mini_controller) +[1.333s] DEBUG:colcon.colcon_parallel_executor.executor.parallel:closing loop +[1.334s] DEBUG:colcon.colcon_parallel_executor.executor.parallel:loop closed +[1.334s] DEBUG:colcon.colcon_parallel_executor.executor.parallel:run_until_complete finished with '0' +[1.334s] DEBUG:colcon.colcon_core.event_reactor:joining thread +[1.341s] INFO:colcon.colcon_core.plugin_system:Skipping extension 'colcon_notification.desktop_notification.terminal_notifier': Not used on non-Darwin systems +[1.341s] INFO:colcon.colcon_core.plugin_system:Skipping extension 'colcon_notification.desktop_notification.win32': Not used on non-Windows systems +[1.341s] INFO:colcon.colcon_notification.desktop_notification:Sending desktop notification using 'notify2' +[1.356s] DEBUG:colcon.colcon_core.event_reactor:joined thread +[1.358s] INFO:colcon.colcon_core.shell:Creating prefix script '/home/ubuntu/ros2_ws/install/local_setup.ps1' +[1.359s] INFO:colcon.colcon_core.shell:Creating prefix util module '/home/ubuntu/ros2_ws/install/_local_setup_util_ps1.py' +[1.361s] INFO:colcon.colcon_core.shell:Creating prefix chain script '/home/ubuntu/ros2_ws/install/setup.ps1' +[1.362s] INFO:colcon.colcon_core.shell:Creating prefix script '/home/ubuntu/ros2_ws/install/local_setup.sh' +[1.363s] INFO:colcon.colcon_core.shell:Creating prefix util module '/home/ubuntu/ros2_ws/install/_local_setup_util_sh.py' +[1.363s] INFO:colcon.colcon_core.shell:Creating prefix chain script '/home/ubuntu/ros2_ws/install/setup.sh' +[1.365s] INFO:colcon.colcon_core.shell:Creating prefix script '/home/ubuntu/ros2_ws/install/local_setup.bash' +[1.366s] INFO:colcon.colcon_core.shell:Creating prefix chain script '/home/ubuntu/ros2_ws/install/setup.bash' +[1.367s] INFO:colcon.colcon_core.shell:Creating prefix script '/home/ubuntu/ros2_ws/install/local_setup.zsh' +[1.368s] INFO:colcon.colcon_core.shell:Creating prefix chain script '/home/ubuntu/ros2_ws/install/setup.zsh' diff --git a/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/command.log b/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/command.log new file mode 100644 index 0000000..4f3d645 --- /dev/null +++ b/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/command.log @@ -0,0 +1,2 @@ +Invoking command in '/home/ubuntu/ros2_ws/src/sphero_mini_controller': PYTHONPATH=/home/ubuntu/ros2_ws/build/sphero_mini_controller/prefix_override:/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages:${PYTHONPATH} /usr/bin/python3 setup.py egg_info --egg-base ../../build/sphero_mini_controller build --build-base /home/ubuntu/ros2_ws/build/sphero_mini_controller/build install --record /home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log --single-version-externally-managed +Invoked command in '/home/ubuntu/ros2_ws/src/sphero_mini_controller' returned '0': PYTHONPATH=/home/ubuntu/ros2_ws/build/sphero_mini_controller/prefix_override:/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages:${PYTHONPATH} /usr/bin/python3 setup.py egg_info --egg-base ../../build/sphero_mini_controller build --build-base /home/ubuntu/ros2_ws/build/sphero_mini_controller/build install --record /home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log --single-version-externally-managed diff --git a/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stderr.log b/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stderr.log new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stdout.log b/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stdout.log new file mode 100644 index 0000000..e0f7d75 --- /dev/null +++ b/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stdout.log @@ -0,0 +1,22 @@ +running egg_info +writing ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO +writing dependency_links to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt +writing entry points to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt +writing requirements to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt +writing top-level names to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt +reading manifest file '../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt' +writing manifest file '../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt' +running build +running build_py +copying sphero_mini_controller/my_first_node.py -> /home/ubuntu/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller +running install +running install_lib +copying /home/ubuntu/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node.py -> /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller +byte-compiling /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node.py to my_first_node.cpython-310.pyc +running install_data +running install_egg_info +removing '/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info' (and everything under it) +Copying ../../build/sphero_mini_controller/sphero_mini_controller.egg-info to /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info +running install_scripts +Installing sphero_mini script to /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller +writing list of installed files to '/home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log' diff --git a/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stdout_stderr.log b/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stdout_stderr.log new file mode 100644 index 0000000..e0f7d75 --- /dev/null +++ b/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/stdout_stderr.log @@ -0,0 +1,22 @@ +running egg_info +writing ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO +writing dependency_links to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt +writing entry points to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt +writing requirements to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt +writing top-level names to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt +reading manifest file '../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt' +writing manifest file '../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt' +running build +running build_py +copying sphero_mini_controller/my_first_node.py -> /home/ubuntu/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller +running install +running install_lib +copying /home/ubuntu/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node.py -> /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller +byte-compiling /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node.py to my_first_node.cpython-310.pyc +running install_data +running install_egg_info +removing '/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info' (and everything under it) +Copying ../../build/sphero_mini_controller/sphero_mini_controller.egg-info to /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info +running install_scripts +Installing sphero_mini script to /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller +writing list of installed files to '/home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log' diff --git a/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/streams.log b/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/streams.log new file mode 100644 index 0000000..c3cd893 --- /dev/null +++ b/ros2_ws/log/build_2023-07-05_16-11-36/sphero_mini_controller/streams.log @@ -0,0 +1,24 @@ +[0.614s] Invoking command in '/home/ubuntu/ros2_ws/src/sphero_mini_controller': PYTHONPATH=/home/ubuntu/ros2_ws/build/sphero_mini_controller/prefix_override:/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages:${PYTHONPATH} /usr/bin/python3 setup.py egg_info --egg-base ../../build/sphero_mini_controller build --build-base /home/ubuntu/ros2_ws/build/sphero_mini_controller/build install --record /home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log --single-version-externally-managed +[0.838s] running egg_info +[0.838s] writing ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/PKG-INFO +[0.838s] writing dependency_links to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/dependency_links.txt +[0.838s] writing entry points to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/entry_points.txt +[0.839s] writing requirements to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/requires.txt +[0.839s] writing top-level names to ../../build/sphero_mini_controller/sphero_mini_controller.egg-info/top_level.txt +[0.840s] reading manifest file '../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt' +[0.841s] writing manifest file '../../build/sphero_mini_controller/sphero_mini_controller.egg-info/SOURCES.txt' +[0.842s] running build +[0.842s] running build_py +[0.843s] copying sphero_mini_controller/my_first_node.py -> /home/ubuntu/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller +[0.844s] running install +[0.844s] running install_lib +[0.845s] copying /home/ubuntu/ros2_ws/build/sphero_mini_controller/build/lib/sphero_mini_controller/my_first_node.py -> /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller +[0.846s] byte-compiling /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller/my_first_node.py to my_first_node.cpython-310.pyc +[0.850s] running install_data +[0.851s] running install_egg_info +[0.853s] removing '/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info' (and everything under it) +[0.853s] Copying ../../build/sphero_mini_controller/sphero_mini_controller.egg-info to /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages/sphero_mini_controller-0.0.0-py3.10.egg-info +[0.854s] running install_scripts +[0.870s] Installing sphero_mini script to /home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/sphero_mini_controller +[0.870s] writing list of installed files to '/home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log' +[0.894s] Invoked command in '/home/ubuntu/ros2_ws/src/sphero_mini_controller' returned '0': PYTHONPATH=/home/ubuntu/ros2_ws/build/sphero_mini_controller/prefix_override:/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages:${PYTHONPATH} /usr/bin/python3 setup.py egg_info --egg-base ../../build/sphero_mini_controller build --build-base /home/ubuntu/ros2_ws/build/sphero_mini_controller/build install --record /home/ubuntu/ros2_ws/build/sphero_mini_controller/install.log --single-version-externally-managed diff --git a/ros2_ws/log/latest b/ros2_ws/log/latest new file mode 100644 index 0000000000000000000000000000000000000000..3244609549432b76ee2b151871ad47cc1e1f0f2d GIT binary patch literal 32 ncmeawE2;4D^JdIpNMtBsNM$HyC}D_aNMa~u$YjW2NMQf~iwOsY literal 0 HcmV?d00001 diff --git a/ros2_ws/log/latest_build b/ros2_ws/log/latest_build new file mode 100644 index 0000000000000000000000000000000000000000..ca67e1aa835f2f7f207f34c5b460777363f7af47 GIT binary patch literal 58 zcmeawE2;4D^JYw9C}qfG$YDrfh-WZjFkmoZFlNwYFkmoe&}A@WFkpyhFkrA`&}A?M Hi-1S~MpOvg literal 0 HcmV?d00001 diff --git a/ros2_ws/src/sphero_mini_controller/.vscode/c_cpp_properties.json b/ros2_ws/src/sphero_mini_controller/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..af5a8df --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "browse": { + "databaseFilename": "${default}", + "limitSymbolsToIncludedHeaders": false + }, + "includePath": [ + "/opt/ros/humble/include/**", + "/usr/include/**" + ], + "name": "ROS", + "intelliSenseMode": "gcc-x64", + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu11", + "cppStandard": "c++14" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/ros2_ws/src/sphero_mini_controller/.vscode/settings.json b/ros2_ws/src/sphero_mini_controller/.vscode/settings.json new file mode 100644 index 0000000..2acdf0c --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/.vscode/settings.json @@ -0,0 +1,13 @@ +{ + "python.autoComplete.extraPaths": [ + "/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages", + "/opt/ros/humble/lib/python3.10/site-packages", + "/opt/ros/humble/local/lib/python3.10/dist-packages" + ], + "python.analysis.extraPaths": [ + "/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages", + "/opt/ros/humble/lib/python3.10/site-packages", + "/opt/ros/humble/local/lib/python3.10/dist-packages" + ], + "ros.distro": "humble" +} \ No newline at end of file diff --git a/ros2_ws/src/sphero_mini_controller/package.xml b/ros2_ws/src/sphero_mini_controller/package.xml new file mode 100644 index 0000000..abe0e77 --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/package.xml @@ -0,0 +1,35 @@ +<?xml version="1.0"?> +<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> +<package format="3"> + <name>sphero_mini_controller</name> + <version>0.0.0</version> + <description>TODO: Package description</description> + <maintainer email="ubuntu@todo.todo">ubuntu</maintainer> + <license>TODO: License declaration</license> + + <depend>rclpy</depend> + <depend>sensor_msgs.msg</depend> + <depend>geometry_msgs.msg</depend> + <depend>box</depend> + <depend>numpy</depend> + <depend>treiber</depend> + <depend>bluepy.btle</depend> + <depend>bluepy</depend> + <depend>_constants</depend> + <depend>struct</depend> + <depend>time</depend> + <depend>sys</depend> + <depend>threading</depend> + <depend>cv2</depend> + <depend>queue</depend> + <depend>matplotlib.pyplot</depend> + + <test_depend>ament_copyright</test_depend> + <test_depend>ament_flake8</test_depend> + <test_depend>ament_pep257</test_depend> + <test_depend>python3-pytest</test_depend> + + <export> + <build_type>ament_python</build_type> + </export> +</package> diff --git a/ros2_ws/src/sphero_mini_controller/resource/sphero_conf.json b/ros2_ws/src/sphero_mini_controller/resource/sphero_conf.json new file mode 100644 index 0000000..a490b4b --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/resource/sphero_conf.json @@ -0,0 +1,3 @@ +{ + "MAC_ADDRESS": "C6:69:72:CD:BC:6D" +} diff --git a/ros2_ws/src/sphero_mini_controller/resource/sphero_mini_controller b/ros2_ws/src/sphero_mini_controller/resource/sphero_mini_controller new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/src/sphero_mini_controller/setup.cfg b/ros2_ws/src/sphero_mini_controller/setup.cfg new file mode 100644 index 0000000..54dced3 --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/sphero_mini_controller +[install] +install_scripts=$base/lib/sphero_mini_controller diff --git a/ros2_ws/src/sphero_mini_controller/setup.py b/ros2_ws/src/sphero_mini_controller/setup.py new file mode 100644 index 0000000..60d245a --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/setup.py @@ -0,0 +1,27 @@ +from setuptools import setup + +package_name = 'sphero_mini_controller' + +setup( + name=package_name, + version='0.0.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='ubuntu', + maintainer_email='ubuntu@todo.todo', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + "sphero_mini = sphero_mini_controller.sphero_mini:main" + + ], + }, +) diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/.vscode/c_cpp_properties.json b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..af5a8df --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "browse": { + "databaseFilename": "${default}", + "limitSymbolsToIncludedHeaders": false + }, + "includePath": [ + "/opt/ros/humble/include/**", + "/usr/include/**" + ], + "name": "ROS", + "intelliSenseMode": "gcc-x64", + "compilerPath": "/usr/bin/gcc", + "cStandard": "gnu11", + "cppStandard": "c++14" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/.vscode/settings.json b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/.vscode/settings.json new file mode 100644 index 0000000..c7c0d89 --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "python.autoComplete.extraPaths": [ + "/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages", + "/opt/ros/humble/lib/python3.10/site-packages", + "/opt/ros/humble/local/lib/python3.10/dist-packages" + ], + "python.analysis.extraPaths": [ + "/home/ubuntu/ros2_ws/install/sphero_mini_controller/lib/python3.10/site-packages", + "/opt/ros/humble/lib/python3.10/site-packages", + "/opt/ros/humble/local/lib/python3.10/dist-packages" + ] +} \ No newline at end of file diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/WavefrontPlanner.py b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/WavefrontPlanner.py new file mode 100644 index 0000000..9904dd9 --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/WavefrontPlanner.py @@ -0,0 +1,389 @@ +############################################################################ +# WAVEFRONT ALGORITHM +# Adapted to Python Code By Darin Velarde +# Fri Jan 29 13:56:53 PST 2010 +# from C code from John Palmisano +# (www.societyofrobots.com) +############################################################################ +import cv2 +import numpy as np +import matplotlib.pyplot as plt +import time + +class waveFrontPlanner: + ############################################################################ + # WAVEFRONT ALGORITHM + # Adapted to Python Code By Darin Velarde + # Fri Jan 29 13:56:53 PST 2010 + # from C code from John Palmisano + # (www.societyofrobots.com) + ############################################################################ + + def __init__(self, mapOfWorld, slow=False): + self.__slow = slow + self.__mapOfWorld = mapOfWorld + if str(type(mapOfWorld)).find("numpy") != -1: + #If its a numpy array + self.__height, self.__width = self.__mapOfWorld.shape + else: + self.__height, self.__width = len(self.__mapOfWorld), len(self.__mapOfWorld[0]) + + self.__nothing = 000 + self.__wall = 999 + self.__goal = 1 + self.__path = "PATH" + + self.__finalPath = [] + + #Robot value + self.__robot = 254 + #Robot default Location + self.__robot_x = 0 + self.__robot_y = 0 + + #default goal location + self.__goal_x = 8 + self.__goal_y = 9 + + #temp variables + self.__temp_A = 0 + self.__temp_B = 0 + self.__counter = 0 + self.__steps = 0 #determine how processor intensive the algorithm was + + #when searching for a node with a lower value + self.__minimum_node = 250 + self.__min_node_location = 250 + self.__new_state = 1 + self.__old_state = 1 + self.__reset_min = 250 #above this number is a special (wall or robot) + ########################################################################### + + def setRobotPosition(self, x, y): + """ + Sets the robot's current position + + """ + + self.__robot_x = x + self.__robot_y = y + ########################################################################### + + def setGoalPosition(self, x, y): + """ + Sets the goal position. + + """ + + self.__goal_x = x + self.__goal_y = y + ########################################################################### + + def robotPosition(self): + return (self.__robot_x, self.__robot_y) + ########################################################################### + + def goalPosition(self): + return (self.__goal_x, self.__goal_y) + ########################################################################### + + def run(self, prnt=False): + """ + The entry point for the robot algorithm to use wavefront propagation. + + """ + + path = [] + while self.__mapOfWorld[self.__robot_x][self.__robot_y] != self.__goal: + if self.__steps > 20000: + print ("Cannot find a path.") + return + #find new location to go to + self.__new_state = self.propagateWavefront() + #update location of robot + if self.__new_state == 1: + self.__robot_x -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" % (self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 2: + self.__robot_y += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 3: + self.__robot_x += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 4: + self.__robot_y -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + self.__old_state = self.__new_state + msg = "Found the goal in %i steps:\n" % self.__steps + msg += "mapOfWorld size= %i %i\n\n" % (self.__height, self.__width) + if prnt: + print(msg) + self.printMap() + return path + ########################################################################### + + def propagateWavefront(self, prnt=False): + self.unpropagate() + #Old robot location was deleted, store new robot location in mapOfWorld + self.__mapOfWorld[self.__robot_x][self.__robot_y] = self.__robot + self.__path = self.__robot + #start location to begin scan at goal location + self.__mapOfWorld[self.__goal_x][self.__goal_y] = self.__goal + counter = 0 + while counter < 200: #allows for recycling until robot is found + x = 0 + y = 0 + #time.sleep(0.00001) + #while the mapOfWorld hasnt been fully scanned + while x < self.__height and y < self.__width: + #if this location is a wall or the goal, just ignore it + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal: + #a full trail to the robot has been located, finished! + minLoc = self.minSurroundingNodeValue(x, y) + if minLoc < self.__reset_min and \ + self.__mapOfWorld[x][y] == self.__robot: + if prnt: + print("Finished Wavefront:\n") + self.printMap() + # Tell the robot to move after this return. + return self.__min_node_location + #record a value in to this node + elif self.__minimum_node != self.__reset_min: + #if this isnt here, 'nothing' will go in the location + self.__mapOfWorld[x][y] = self.__minimum_node + 1 + #go to next node and/or row + y += 1 + if y == self.__width and x != self.__height: + x += 1 + y = 0 + #print self.__robot_x, self.__robot_y + if prnt: + print("Sweep #: %i\n" % (counter + 1)) + self.printMap() + self.__steps += 1 + counter += 1 + return 0 + ########################################################################### + + def unpropagate(self): + """ + clears old path to determine new path + stay within boundary + + """ + + for x in range(0, self.__height): + for y in range(0, self.__width): + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal and \ + self.__mapOfWorld[x][y] != self.__path: + #if this location is a wall or goal, just ignore it + self.__mapOfWorld[x][y] = self.__nothing #clear that space + ########################################################################### + + def minSurroundingNodeValue(self, x, y): + """ + this method looks at a node and returns the lowest value around that + node. + + """ + + #reset minimum + self.__minimum_node = self.__reset_min + #down + if x < self.__height -1: + if self.__mapOfWorld[x + 1][y] < self.__minimum_node and \ + self.__mapOfWorld[x + 1][y] != self.__nothing: + #find the lowest number node, and exclude empty nodes (0's) + self.__minimum_node = self.__mapOfWorld[x + 1][y] + self.__min_node_location = 3 + #up + if x > 0: + if self.__mapOfWorld[x-1][y] < self.__minimum_node and \ + self.__mapOfWorld[x-1][y] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x-1][y] + self.__min_node_location = 1 + #right + if y < self.__width -1: + if self.__mapOfWorld[x][y + 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y + 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y + 1] + self.__min_node_location = 2 + #left + if y > 0: + if self.__mapOfWorld[x][y - 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y - 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y-1] + self.__min_node_location = 4 + return self.__minimum_node + ########################################################################### + + def printMap(self): + """ + Prints out the map of this instance of the class. + + """ + + msg = '' + for temp_B in range(0, self.__height): + for temp_A in range(0, self.__width): + if self.__mapOfWorld[temp_B][temp_A] == self.__wall: + msg += "%04s" % "[#]" + elif self.__mapOfWorld[temp_B][temp_A] == self.__robot: + msg += "%04s" % "-" + elif self.__mapOfWorld[temp_B][temp_A] == self.__goal: + msg += "%04s" % "G" + else: + msg += "%04s" % str(self.__mapOfWorld[temp_B][temp_A]) + msg += "\n\n" + msg += "\n\n" + print(msg) + # + if self.__slow == True: + time.sleep(0.05) +############################################################################ + +class mapCreate: + ############################################################################ + + def create_map(self, scale,img): + + print('Original Dimensions : ',img.shape) + + scale_percent = 100-scale # percent of original size + width = int(img.shape[1] * scale_percent / 100) + height = int(img.shape[0] * scale_percent / 100) + dim = (width, height) + + # resize image + resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA) + print('Resized Dimensions : ',resized.shape) + + gray = cv2.cvtColor(resized, cv2.COLOR_RGB2GRAY) + mask = cv2.inRange(gray, np.array([200]), np.array([255])) + + mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + mask_list= np.ndarray.tolist(mask) + + #Markiere alle leeren Zellen mit 000 und alle Zellen mit Hinderniss mit 999 + for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['999' if j > 200 else '000' for j in mask_list[i]] + + mapOfWorld = np.array(mapOfWorld, dtype = int) + mapOfWorldSized = mapOfWorld.copy() + + e = 3 + #Hindernisse Größer machen + for i in range(0,mapOfWorld.shape[0]): + for j in range(0,mapOfWorld.shape[1]): + for r in range(0,e): + try: + if (mapOfWorldSized[i][j] == 999): + mapOfWorld[i][j+e] = 999 + mapOfWorld[i+e][j] = 999 + mapOfWorld[i-e][j] = 999 + mapOfWorld[i][j-e] = 999 + mapOfWorld[i+e][j+e] = 999 + mapOfWorld[i+e][j-e] = 999 + mapOfWorld[i-e][j+e] = 999 + mapOfWorld[i-e][j-e] = 999 + except Exception: + continue + + return mapOfWorld + ############################################################################ + + def scale_koord(self, sx, sy, gx, gy,scale): + scale_percent = 100-scale # percent of original size + + sx = int(sx*scale_percent/100) + sy = int(sy*scale_percent/100) + gx = int(gx*scale_percent/100) + gy = int(gy*scale_percent/100) + + return sx,sy,gx,gy + ############################################################################ + + def rescale_koord(self, path,scale): + scale_percent = 100-scale # percent of original size100 + + path = np.array(path) + + for i in range(len(path)): + path[i] = path[i]*100/scale_percent + + return path + ############################################################################ + + def calc_trans(self, x, y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans +############################################################################### + +if __name__ == "__main__": + """ + X is vertical, Y is horizontal + + """ + + Map = mapCreate() #Map Objekt erzeugen + + #Verkleinerungsfaktor in % + scale = 90 + + img = cv2.imread("D:/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/karte.jpg", cv2.COLOR_RGB2GRAY) + #cap = cv2.VideoCapture(2) + mapWorld = Map.create_map(scale, img) + height, width = img.shape[:2] + + #Angaben in Weltkoordinaten + sy = 300 #X-Koordinate im Weltkoordinaten System + sx = 200 #Y-Koordinate im Weltkoordinaten System + + gy = 700 #X-Koordinate im Weltkordinaten System + gx = 240 #Y-Koordinate im Weltkoordinaten System + + sx,sy,gx,gy = Map.scale_koord(sx,sy,gx,gy,scale) #Kordinaten Tauschen X,Y + + start = time.time() + planner = waveFrontPlanner(mapWorld) + planner.setGoalPosition(gx,gy) + planner.setRobotPosition(sx,sy) + path = planner.run(False) + end = time.time() + print("Took %f seconds to run wavefront simulation" % (end - start)) + + path = Map.rescale_koord(path, scale) + #print(path) +#%% Plot + #Programm Koordinaten + imgTrans = cv2.transpose(img) # X und Y-Achse im Bild tauschen + imgPlot, ax1 = plt.subplots() + + ax1.set_title('Programm Koordinaten') + ax1.imshow(imgTrans) + ax1.set_xlabel('[px]') + ax1.set_ylabel('[px]') + ax1.scatter(path[:,0], path[:,1], color='r') + + #Bild Koordinaten + imgPlot2, ax2 = plt.subplots() + ax2.set_title('Bild Koordinaten') + ax2.set_xlabel('[px]') + ax2.set_ylabel('[px]') + ax2.imshow(img) + ax2.scatter(path[:,1], path[:,0], color='r') + +#%% Sphero Pfad + pathWeltX, pathWeltY = Map.calc_trans(path[:,1], path[:,0], height) \ No newline at end of file diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__init__.py b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__pycache__/_constants.cpython-310.pyc b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__pycache__/_constants.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7a7dc990c992e83d0131942566db7b7d4f80a057 GIT binary patch literal 2282 zcmd1j<>g{vU|?`?cT9Q1%fRp$#6iX&SqBCNhT=&K3=Am@Q4A@JDNHHMDJ&_hDQqe1 zDI6)BDO__nqZm`TQ+QH%Q<ziuQutE@=CDOEr3j`7r3lYqiegR?NfFItjABXQNfApC zPmxHGoWl~u3f3W&BAp_W!k8kSBAX&Nha-v&Y>Ie_Sc-g#LW<%XwkY-#?i8^UrWC#u z<`ktA<rLNwg%p)JoKYMps=*AJYKi-p85p>{^YY8{6arF<G7B<NixP7bLPI@Wiggt7 zlS&dZ^HNh3k}4GnOH+#~GxO3FN{c~cNk*!IQ%-4WL8U@&eoARhsuh<lH2{}mUP-2c zi+`|!b4FrOVsc4pQD$*TX0n2ffq{XMfq{XkuBo|6nyyKjk%6wMvAKb+nT46Dxsh3# zk)e^H9hXyLNl9u^rGigtS!xc387Q)LTu43+C`wIBEh<V)QE<-B%S%lz$;{7F2uLhS z%uOvxEh@$^iFmDcT#f;r3SmYVMi>|v7=k>7-$O2Lp&%!f6y;-j2c#Q>@tfe6n45}W zxl3wUW^$?mNY0MSv7jI|u_!Sw8B-lX%8m=<6wkc0d<?@tZZI$~Fv0JR;M5|poz5AF zdFh~lMvGuHc{{GqylhayG{j|}F@F1?+HnRv0qtBN8L0|Isl_D<`DqHE<X)DTQ<_?= zP@Gtnnxc@I2Np}o1gDh5qRiA{g{0J+{Bj+I^3<YKg{1tVqWtpI6os^+{9G=F%=D!E zg3MI)Vuh^4vc%+~%z_eyoXn)6#G*<ah4PHd<P3$(Vui%A#LS$;q?}ZR#1e&!l9GaA zD}DX+%#w`KB)#PPTz#l1`o*AZnqOw5msQNg^-_R=f#Ibfh!6%5Vjw~sL`Z-LNf03g zBIFqu7&JL=@gx>x1{CEdrxq9I7v172t}HG|%>_m4EzW}c^3)<QlRc#<vn(?&{T5GR zUS@7$Nl|`YW^(Z@w&K)0kS@;R{M^)%43Gl0(&E%2Pybsya3RM4&zGP=@FgS2RyGhJ z1R^v*_Osm*%T3J8b1W#x$xKcJ<+8BUqT<Z_yjy}v`S~R|`H3m1MNpw4kk4*$!3{P- zVw)kc&0qF{%)bFLpXnBFa7kiOiGNx^VsdtB$t`Zzyc9TxJuxT8$JM3yB_Buw6Nq2| z5gZ_`EVsBai-S^&3-a@dQ*Q|rr52W^7MCDIBw-@{c{!D?MMe2VaK++9sl}-!#h!VI z$t9U(nI)AWnYpR?r6n(!L8kM92v!gw2eOCb7H3LoYC&*LYHGnP)?zTlmjo%d!t!%U z64O&}v6Lrfr-J=o1d2N(&l)4KO<uBtOyvQY%65yRD6!l(za+ot77y4t9;t~bpcvu> z#bv5vaWFX5+~S0CgOYCvfSKW$B^hwF#U+VJnK_wN;HY^i0y3T-WIX3BE=b(_CKhMk z;)Af^{ufHl&r8cpFD*)S&d<ro1jnaXGK}exT9TSvlA7XJT$z^)b(~Lraw5p7BB^<x z1P|8+QNs=~-|!ZcVRTCrZbL9Q2?m!Gr6%V3X>#6TkB?8uPmYhj#gPIo(>z^n2?fIw zwR3)MZem_a@h#!v)Vvf(nsCm~D=taQD=EGu07<-1MV>Cjw^-A164Q%s@q@Dhk~lvo zs50}?p+d#5Ab{~#G86?cFhGc3A^I8lxvBc4Nu_xurTRtr#YXYv#rnlX$<Uk|pPQMN z8K0b=2P)NbQj2g2gOosAs#j2Xi^B#|)7pXZRdE&r0|N^KlL!+dDrEb?#Dx%H`_078 l#K`uagPDm5BFgrci50?vk!(MiIALriMu-jsAH?GN0RU6~)o=g+ literal 0 HcmV?d00001 diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__pycache__/core.cpython-310.pyc b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__pycache__/core.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..268efa0a4ba0ab4916f275a675473967f0d05ef1 GIT binary patch literal 19045 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 RfVO9GF!C`m)Ptay7XV!eD4zfT literal 0 HcmV?d00001 diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__pycache__/simple_moves.cpython-310.pyc b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/__pycache__/simple_moves.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1aac01a6d648c63bd3f5ffbf474fde28d479087f GIT binary patch literal 791 zcmd1j<>g{vU|^V=?v^r}g@NHQh=Yuo7#J8F7#J9eB^VeOQW&BbQW#U1au}l+Q<zg& zS{S03Qka4nG+C>vxq>ru3vyBwa`Veli@9`>02fzCMrN@>ZhlH>PO3t3eqKppW?r#E zT4`Q#NoIatu|i3{LPla)szOOdszPu<Mru*MLT+YWrb0@-LUDd>szNcs<lNM}l41po zRK0XPh2+el<eXGZJ+7A^2m2*6f+#2ku~``y7@R>)KgPhoP{WV~aylc(`Mu1H3^fc3 z7*m)RGJ;u5DJ)PHa|$bz#j=nAEXSC_1{GmSVT7=tCb6V2K}}*!VNYRP$OKl8Fo89N z5lNH-NiPeMD_B!FkyOEK$D%idA(%mv%dZIJHBIJQ%tfgww^-AQQd9G8u_Wb`rn<fS z&%nU&l9_>lA>?HWhzU}eIp_cX|C(&KxQkOud|X|e^K<fxZt)hEBqn9%WL71DBH)&A zacYTEVsf^RtBYq|NornkW=Z8Omh!~Rl3P4Qsl}-!9;t~bnR)5ASc>v<ax@ulu@!@3 zX(dCE00RTVuQ2_L{M=Oi(xlS7l2ZMm{9>c{@?!nsqGWxDx_D4*#)Bdrq(8L?r!Y94 z<H0FNub}c4ds==`d16rtD1{a)F)%O)FmW-0AP1NfVCG@uV&Y-qViaKFVdP@uVd7&f z5@KLr&}6>F9v`2QpBx{5i@CV67%T{ODiR^cz`$^e!zLG;D(yf)Rm{e~z`(-5!Op`3 E0HgTKSO5S3 literal 0 HcmV?d00001 diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/_constants.py b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/_constants.py new file mode 100644 index 0000000..fa8a0e8 --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/_constants.py @@ -0,0 +1,72 @@ +''' +Known Peripheral UUIDs, obtained by querying using the Bluepy module: +===================================================================== +Anti DOS Characteristic <00020005-574f-4f20-5370-6865726f2121> +Battery Level Characteristic <Battery Level> +Peripheral Preferred Connection Parameters Characteristic <Peripheral Preferred Connection Parameters> +API V2 Characteristic <00010002-574f-4f20-5370-6865726f2121> +DFU Control Characteristic <00020002-574f-4f20-5370-6865726f2121> +Name Characteristic <Device Name> +Appearance Characteristic <Appearance> +DFU Info Characteristic <00020004-574f-4f20-5370-6865726f2121> +Service Changed Characteristic <Service Changed> +Unknown1 Characteristic <00020003-574f-4f20-5370-6865726f2121> +Unknown2 Characteristic <00010003-574f-4f20-5370-6865726f2121> + +The rest of the values saved in the dictionaries below, were borrowed from +@igbopie's javacript library, which is available at https://github.com/igbopie/spherov2.js + +''' + +deviceID = {"apiProcessor": 0x10, # 16 + "systemInfo": 0x11, # 17 + "powerInfo": 0x13, # 19 + "driving": 0x16, # 22 + "animatronics": 0x17, # 23 + "sensor": 0x18, # 24 + "something": 0x19, # 25 + "userIO": 0x1a, # 26 + "somethingAPI": 0x1f} # 31 + +SystemInfoCommands = {"mainApplicationVersion": 0x00, # 00 + "bootloaderVersion": 0x01, # 01 + "something": 0x06, # 06 + "something2": 0x13, # 19 + "something6": 0x12, # 18 + "something7": 0x28} # 40 + +sendPacketConstants = {"StartOfPacket": 0x8d, # 141 + "EndOfPacket": 0xd8} # 216 + +userIOCommandIDs = {"allLEDs": 0x0e} # 14 + +flags= {"isResponse": 0x01, # 0x01 + "requestsResponse": 0x02, # 0x02 + "requestsOnlyErrorResponse": 0x04, # 0x04 + "resetsInactivityTimeout": 0x08} # 0x08 + +powerCommandIDs={"deepSleep": 0x00, # 0 + "sleep": 0x01, # 01 + "batteryVoltage": 0x03, # 03 + "wake": 0x0D, # 13 + "something": 0x05, # 05 + "something2": 0x10, # 16 + "something3": 0x04, # 04 + "something4": 0x1E} # 30 + +drivingCommands={"rawMotor": 0x01, # 1 + "resetHeading": 0x06, # 6 + "driveAsSphero": 0x04, # 4 + "driveAsRc": 0x02, # 2 + "driveWithHeading": 0x07, # 7 + "stabilization": 0x0C} # 12 + +sensorCommands={'sensorMask': 0x00, # 00 + 'sensorResponse': 0x02, # 02 + 'configureCollision': 0x11, # 17 + 'collisionDetectedAsync': 0x12, # 18 + 'resetLocator': 0x13, # 19 + 'enableCollisionAsync': 0x14, # 20 + 'sensor1': 0x0F, # 15 + 'sensor2': 0x17, # 23 + 'configureSensorStream': 0x0C} # 12 diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/blobErkennung.py b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/blobErkennung.py new file mode 100644 index 0000000..4b0c0cd --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/blobErkennung.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 + +# Standard imports +import cv2 +import numpy as np; + +# Img Objekt +#cap = cv2.VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") +cap = cv2.VideoCapture(4) + +# Set up the detector with default parameters. +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +params.filterByArea = True +params.minArea = 20 +params.maxArea = 200 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +params.filterByInertia = True +params.minInertiaRatio = 0.5 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) + +while True: + + success, img = cap.read() + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + + # Detect blobs. + keypoints = detector.detect(gray) + + #print(keypoints) + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + + im_with_keypoints = cv2.drawKeypoints(gray, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS) + + # Show blobs + cv2.imshow("Keypoints", im_with_keypoints) + + if cv2.waitKey(10) & 0xFF == ord('q'): + break \ No newline at end of file diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/coreROS2.py b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/coreROS2.py new file mode 100644 index 0000000..ec34350 --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/coreROS2.py @@ -0,0 +1,638 @@ +from bluepy.btle import Peripheral +from bluepy import btle +from sphero_mini_controller._constants import * +import struct +import time +import sys + +class SpheroMini(): + def __init__(self, MACAddr, verbosity = 4, user_delegate = None): + ''' + initialize class instance and then build collect BLE sevices and characteristics. + Also sends text string to Anti-DOS characteristic to prevent returning to sleep, + and initializes notifications (which is what the sphero uses to send data back to + the client). + ''' + self.verbosity = verbosity # 0 = Silent, + # 1 = Connection/disconnection only + # 2 = Init messages + # 3 = Recieved commands + # 4 = Acknowledgements + self.sequence = 1 + self.v_batt = None # will be updated with battery voltage when sphero.getBatteryVoltage() is called + self.firmware_version = [] # will be updated with firware version when sphero.returnMainApplicationVersion() is called + + if self.verbosity > 0: + print("[INFO] Connecting to ", MACAddr) + self.p = Peripheral(MACAddr, "random") #connect + + if self.verbosity > 1: + print("[INIT] Initializing") + + # Subscribe to notifications + self.sphero_delegate = MyDelegate(self, user_delegate) # Pass a reference to this instance when initializing + self.p.setDelegate(self.sphero_delegate) + + if self.verbosity > 1: + print("[INIT] Read all characteristics and descriptors") + # Get characteristics and descriptors + self.API_V2_characteristic = self.p.getCharacteristics(uuid="00010002-574f-4f20-5370-6865726f2121")[0] + self.AntiDOS_characteristic = self.p.getCharacteristics(uuid="00020005-574f-4f20-5370-6865726f2121")[0] + self.DFU_characteristic = self.p.getCharacteristics(uuid="00020002-574f-4f20-5370-6865726f2121")[0] + self.DFU2_characteristic = self.p.getCharacteristics(uuid="00020004-574f-4f20-5370-6865726f2121")[0] + self.API_descriptor = self.API_V2_characteristic.getDescriptors(forUUID=0x2902)[0] + self.DFU_descriptor = self.DFU_characteristic.getDescriptors(forUUID=0x2902)[0] + + # The rest of this sequence was observed during bluetooth sniffing: + # Unlock code: prevent the sphero mini from going to sleep again after 10 seconds + if self.verbosity > 1: + print("[INIT] Writing AntiDOS characteristic unlock code") + self.AntiDOS_characteristic.write("usetheforce...band".encode(), withResponse=True) + + # Enable DFU notifications: + if self.verbosity > 1: + print("[INIT] Configuring DFU descriptor") + self.DFU_descriptor.write(struct.pack('<bb', 0x01, 0x00), withResponse = True) + + # No idea what this is for. Possibly a device ID of sorts? Read request returns '00 00 09 00 0c 00 02 02': + if self.verbosity > 1: + print("[INIT] Reading DFU2 characteristic") + _ = self.DFU2_characteristic.read() + + # Enable API notifications: + if self.verbosity > 1: + print("[INIT] Configuring API dectriptor") + self.API_descriptor.write(struct.pack('<bb', 0x01, 0x00), withResponse = True) + + self.wake() + + # Finished initializing: + if self.verbosity > 1: + print("[INIT] Initialization complete\n") + + def disconnect(self): + if self.verbosity > 0: + print("[INFO] Disconnecting") + + self.p.disconnect() + + def wake(self): + ''' + Bring device out of sleep mode (can only be done if device was in sleep, not deep sleep). + If in deep sleep, the device should be connected to USB power to wake. + ''' + if self.verbosity > 2: + print("[SEND {}] Waking".format(self.sequence)) + + self._send(characteristic=self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=powerCommandIDs["wake"], + payload=[]) # empty payload + + self.getAcknowledgement("Wake") + + def sleep(self, deepSleep=False): + ''' + Put device to sleep or deep sleep (deep sleep needs USB power connected to wake up) + ''' + if deepSleep: + sleepCommID=powerCommandIDs["deepSleep"] + if self.verbosity > 0: + print("[INFO] Going into deep sleep. Connect USB power to wake.") + else: + sleepCommID=powerCommandIDs["sleep"] + self._send(characteristic=self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=sleepCommID, + payload=[]) #empty payload + + def setLEDColor(self, red = None, green = None, blue = None): + ''' + Set device LED color based on RGB vales (each can range between 0 and 0xFF) + ''' + if self.verbosity > 2: + print("[SEND {}] Setting main LED colour to [{}, {}, {}]".format(self.sequence, red, green, blue)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['userIO'], # 0x1a + commID = userIOCommandIDs["allLEDs"], # 0x0e + payload = [0x00, 0x0e, red, green, blue]) + + self.getAcknowledgement("LED/backlight") + + def setBackLEDIntensity(self, brightness=None): + ''' + Set device LED backlight intensity based on 0-255 values + + NOTE: this is not the same as aiming - it only turns on the LED + ''' + if self.verbosity > 2: + print("[SEND {}] Setting backlight intensity to {}".format(self.sequence, brightness)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['userIO'], + commID = userIOCommandIDs["allLEDs"], + payload = [0x00, 0x01, brightness]) + + self.getAcknowledgement("LED/backlight") + + def roll(self, speed=None, heading=None): + ''' + Start to move the Sphero at a given direction and speed. + heading: integer from 0 - 360 (degrees) + speed: Integer from 0 - 255 + + Note: the zero heading should be set at startup with the resetHeading method. Otherwise, it may + seem that the sphero doesn't honor the heading argument + ''' + if self.verbosity > 2: + print("[SEND {}] Rolling with speed {} and heading {}".format(self.sequence, speed, heading)) + + if abs(speed) > 255: + print("WARNING: roll speed parameter outside of allowed range (-255 to +255)") + + if speed < 0: + speed = -1*speed+256 # speed values > 256 in the send packet make the spero go in reverse + + speedH = (speed & 0xFF00) >> 8 + speedL = speed & 0xFF + headingH = (heading & 0xFF00) >> 8 + headingL = heading & 0xFF + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['driving'], + commID = drivingCommands["driveWithHeading"], + payload = [speedL, headingH, headingL, speedH]) + + self.getAcknowledgement("Roll") + + def resetHeading(self): + ''' + Reset the heading zero angle to the current heading (useful during aiming) + Note: in order to manually rotate the sphero, you need to call stabilization(False). + Once the heading has been set, call stabilization(True). + ''' + if self.verbosity > 2: + print("[SEND {}] Resetting heading".format(self.sequence)) + + self._send(characteristic = self.API_V2_characteristic, + devID = deviceID['driving'], + commID = drivingCommands["resetHeading"], + payload = []) #empty payload + + self.getAcknowledgement("Heading") + + def returnMainApplicationVersion(self): + ''' + Sends command to return application data in a notification + ''' + if self.verbosity > 2: + print("[SEND {}] Requesting firmware version".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID = deviceID['systemInfo'], + commID = SystemInfoCommands['mainApplicationVersion'], + payload = []) # empty + + self.getAcknowledgement("Firmware") + + def getBatteryVoltage(self): + ''' + Sends command to return battery voltage data in a notification. + Data printed to console screen by the handleNotifications() method in the MyDelegate class. + ''' + if self.verbosity > 2: + print("[SEND {}] Requesting battery voltage".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['powerInfo'], + commID=powerCommandIDs['batteryVoltage'], + payload=[]) # empty + + self.getAcknowledgement("Battery") + + def stabilization(self, stab = True): + ''' + Sends command to turn on/off the motor stabilization system (required when manually turning/aiming the sphero) + ''' + if stab == True: + if self.verbosity > 2: + print("[SEND {}] Enabling stabilization".format(self.sequence)) + val = 1 + else: + if self.verbosity > 2: + print("[SEND {}] Disabling stabilization".format(self.sequence)) + val = 0 + self._send(self.API_V2_characteristic, + devID=deviceID['driving'], + commID=drivingCommands['stabilization'], + payload=[val]) + + self.getAcknowledgement("Stabilization") + + def wait(self, delay): + ''' + This is a non-blocking delay command. It is similar to time.sleep(), except it allows asynchronous + notification handling to still be performed. + ''' + start = time.time() + while(1): + self.p.waitForNotifications(0.001) + if time.time() - start > delay: + break + + def _send(self, characteristic=None, devID=None, commID=None, payload=[]): + ''' + A generic "send" method, which will be used by other methods to send a command ID, payload and + appropriate checksum to a specified device ID. Mainly useful because payloads are optional, + and can be of varying length, to convert packets to binary, and calculate and send the + checksum. For internal use only. + + Packet structure has the following format (in order): + + - Start byte: always 0x8D + - Flags byte: indicate response required, etc + - Virtual device ID: see _constants.py + - Command ID: see _constants.py + - Sequence number: Seems to be arbitrary. I suspect it is used to match commands to response packets (in which the number is echoed). + - Payload: Could be varying number of bytes (incl. none), depending on the command + - Checksum: See below for calculation + - End byte: always 0xD8 + + ''' + sendBytes = [sendPacketConstants["StartOfPacket"], + sum([flags["resetsInactivityTimeout"], flags["requestsResponse"]]), + devID, + commID, + self.sequence] + payload # concatenate payload list + + self.sequence += 1 # Increment sequence number, ensures we can identify response packets are for this command + if self.sequence > 255: + self.sequence = 0 + + # Compute and append checksum and add EOP byte: + # From Sphero docs: "The [checksum is the] modulo 256 sum of all the bytes + # from the device ID through the end of the data payload, + # bit inverted (1's complement)" + # For the sphero mini, the flag bits must be included too: + checksum = 0 + for num in sendBytes[1:]: + checksum = (checksum + num) & 0xFF # bitwise "and to get modulo 256 sum of appropriate bytes + checksum = 0xff - checksum # bitwise 'not' to invert checksum bits + sendBytes += [checksum, sendPacketConstants["EndOfPacket"]] # concatenate + + # Convert numbers to bytes + output = b"".join([x.to_bytes(1, byteorder='big') for x in sendBytes]) + + #send to specified characteristic: + characteristic.write(output, withResponse = True) + + def getAcknowledgement(self, ack): + #wait up to 10 secs for correct acknowledgement to come in, including sequence number! + start = time.time() + while(1): + self.p.waitForNotifications(1) + if self.sphero_delegate.notification_seq == self.sequence-1: # use one less than sequence, because _send function increments it for next send. + if self.verbosity > 3: + print("[RESP {}] {}".format(self.sequence-1, self.sphero_delegate.notification_ack)) + self.sphero_delegate.clear_notification() + break + elif self.sphero_delegate.notification_seq >= 0: + print("Unexpected ACK. Expected: {}/{}, received: {}/{}".format( + ack, self.sequence, self.sphero_delegate.notification_ack.split()[0], + self.sphero_delegate.notification_seq) + ) + if time.time() > start + 10: + print("Timeout waiting for acknowledgement: {}/{}".format(ack, self.sequence)) + break + +# ======================================================================= +# The following functions are experimental: +# ======================================================================= + + def configureCollisionDetection(self, + xThreshold = 50, + yThreshold = 50, + xSpeed = 50, + ySpeed = 50, + deadTime = 50, # in 10 millisecond increments + method = 0x01, # Must be 0x01 + callback = None): + ''' + Appears to function the same as other Sphero models, however speed settings seem to have no effect. + NOTE: Setting to zero seems to cause bluetooth errors with the Sphero Mini/bluepy library - set to + 255 to make it effectively disabled. + + deadTime disables future collisions for a short period of time to avoid repeat triggering by the same + event. Set in 10ms increments. So if deadTime = 50, that means the delay will be 500ms, or half a second. + + From Sphero docs: + + xThreshold/yThreshold: An 8-bit settable threshold for the X (left/right) and Y (front/back) axes + of Sphero. + + xSpeed/ySpeed: An 8-bit settable speed value for the X and Y axes. This setting is ranged by the + speed, then added to xThreshold, yThreshold to generate the final threshold value. + ''' + + if self.verbosity > 2: + print("[SEND {}] Configuring collision detection".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['configureCollision'], + payload=[method, xThreshold, xSpeed, yThreshold, ySpeed, deadTime]) + + self.collision_detection_callback = callback + + self.getAcknowledgement("Collision") + + def configureSensorStream(self): # Use default values + ''' + Send command to configure sensor stream using default values as found during bluetooth + sniffing of the Sphero Edu app. + + Must be called after calling configureSensorMask() + ''' + bitfield1 = 0b00000000 # Unknown function - needs experimenting + bitfield2 = 0b00000000 # Unknown function - needs experimenting + bitfield3 = 0b00000000 # Unknown function - needs experimenting + bitfield4 = 0b00000000 # Unknown function - needs experimenting + + if self.verbosity > 2: + print("[SEND {}] Configuring sensor stream".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['configureSensorStream'], + payload=[bitfield1, bitfield1, bitfield1, bitfield1]) + + self.getAcknowledgement("Sensor") + + def configureSensorMask(self, + sample_rate_divisor = 0x25, # Must be > 0 + packet_count = 0, + IMU_pitch = False, + IMU_roll = False, + IMU_yaw = False, + IMU_acc_x = False, + IMU_acc_y = False, + IMU_acc_z = False, + IMU_gyro_x = False, + IMU_gyro_y = False, + IMU_gyro_z = False): + + ''' + Send command to configure sensor mask using default values as found during bluetooth + sniffing of the Sphero Edu app. From experimentation, it seems that these are he functions of each: + + Sampling_rate_divisor. Slow data EG: Set to 0x32 to the divide data rate by 50. Setting below 25 (0x19) causes + bluetooth errors + + Packet_count: Select the number of packets to transmit before ending the stream. Set to zero to stream infinitely + + All IMU bool parameters: Toggle transmission of that value on or off (e.g. set IMU_acc_x = True to include the + X-axis accelerometer readings in the sensor stream) + ''' + + # Construct bitfields based on function parameters: + IMU_bitfield1 = (IMU_pitch<<2) + (IMU_roll<<1) + IMU_yaw + IMU_bitfield2 = ((IMU_acc_y<<7) + (IMU_acc_z<<6) + (IMU_acc_x<<5) + \ + (IMU_gyro_y<<4) + (IMU_gyro_x<<2) + (IMU_gyro_z<<2)) + + if self.verbosity > 2: + print("[SEND {}] Configuring sensor mask".format(self.sequence)) + + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensorMask'], + payload=[0x00, # Unknown param - altering it seems to slow data rate. Possibly averages multiple readings? + sample_rate_divisor, + packet_count, # Packet count: select the number of packets to stop streaming after (zero = infinite) + 0b00, # Unknown param: seems to be another accelerometer bitfield? Z-acc, Y-acc + IMU_bitfield1, + IMU_bitfield2, + 0b00]) # reserved, Position?, Position?, velocity?, velocity?, Y-gyro, timer, reserved + + self.getAcknowledgement("Mask") + + ''' + Since the sensor values arrive as unlabelled lists in the order that they appear in the bitfields above, we need + to create a list of sensors that have been configured.Once we have this list, then in the default_delegate class, + we can get sensor values as attributes of the sphero_mini class. + e.g. print(sphero.IMU_yaw) # displays the current yaw angle + ''' + + # Initialize dictionary with sensor names as keys and their bool values (set by the user) as values: + availableSensors = {"IMU_pitch" : IMU_pitch, + "IMU_roll" : IMU_roll, + "IMU_yaw" : IMU_yaw, + "IMU_acc_y" : IMU_acc_y, + "IMU_acc_z" : IMU_acc_z, + "IMU_acc_x" : IMU_acc_x, + "IMU_gyro_y" : IMU_gyro_y, + "IMU_gyro_x" : IMU_gyro_x, + "IMU_gyro_z" : IMU_gyro_z} + + # Create list of of only sensors that have been "activated" (set as true in the method arguments): + self.configured_sensors = [name for name in availableSensors if availableSensors[name] == True] + + def sensor1(self): # Use default values + ''' + Unknown function. Observed in bluetooth sniffing. + ''' + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensor1'], + payload=[0x01]) + + self.getAcknowledgement("Sensor1") + + def sensor2(self): # Use default values + ''' + Unknown function. Observed in bluetooth sniffing. + ''' + self._send(self.API_V2_characteristic, + devID=deviceID['sensor'], + commID=sensorCommands['sensor2'], + payload=[0x00]) + + self.getAcknowledgement("Sensor2") + +# ======================================================================= + +class MyDelegate(btle.DefaultDelegate): + + ''' + This class handles notifications (both responses and asynchronous notifications). + + Usage of this class is described in the Bluepy documentation + + ''' + + def __init__(self, sphero_class, user_delegate): + self.sphero_class = sphero_class # for saving sensor values as attributes of sphero class instance + self.user_delegate = user_delegate # to directly notify users of callbacks + btle.DefaultDelegate.__init__(self) + self.clear_notification() + self.notificationPacket = [] + + def clear_notification(self): + self.notification_ack = "DEFAULT ACK" + self.notification_seq = -1 + + def bits_to_num(self, bits): + ''' + This helper function decodes bytes from sensor packets into single precision floats. Encoding follows the + the IEEE-754 standard. + ''' + num = int(bits, 2).to_bytes(len(bits) // 8, byteorder='little') + num = struct.unpack('f', num)[0] + return num + + def handleNotification(self, cHandle, data): + ''' + This method acts as an interrupt service routine. When a notification comes in, this + method is invoked, with the variable 'cHandle' being the handle of the characteristic that + sent the notification, and 'data' being the payload (sent one byte at a time, so the packet + needs to be reconstructed) + + The method keeps appending bytes to the payload packet byte list until end-of-packet byte is + encountered. Note that this is an issue, because 0xD8 could be sent as part of the payload of, + say, the battery voltage notification. In future, a more sophisticated method will be required. + ''' + # Allow the user to intercept and process data first.. + if self.user_delegate != None: + if self.user_delegate.handleNotification(cHandle, data): + return + + #print("Received notification with packet ", str(data)) + + for data_byte in data: # parse each byte separately (sometimes they arrive simultaneously) + + self.notificationPacket.append(data_byte) # Add new byte to packet list + + # If end of packet (need to find a better way to segment the packets): + if data_byte == sendPacketConstants['EndOfPacket']: + # Once full the packet has arrived, parse it: + # Packet structure is similar to the outgoing send packets (see docstring in sphero_mini._send()) + + # Attempt to unpack. Might fail if packet is too badly corrupted + try: + start, flags_bits, devid, commcode, seq, *notification_payload, chsum, end = self.notificationPacket + except ValueError: + print("Warning: notification packet unparseable ", self.notificationPacket) + self.notificationPacket = [] # Discard this packet + return # exit + + # Compute and append checksum and add EOP byte: + # From Sphero docs: "The [checksum is the] modulo 256 sum of all the bytes + # from the device ID through the end of the data payload, + # bit inverted (1's complement)" + # For the sphero mini, the flag bits must be included too: + checksum_bytes = [flags_bits, devid, commcode, seq] + notification_payload + checksum = 0 # init + for num in checksum_bytes: + checksum = (checksum + num) & 0xFF # bitwise "and to get modulo 256 sum of appropriate bytes + checksum = 0xff - checksum # bitwise 'not' to invert checksum bits + if checksum != chsum: # check computed checksum against that recieved in the packet + print("Warning: notification packet checksum failed - ", str(self.notificationPacket)) + self.notificationPacket = [] # Discard this packet + return # exit + + # Check if response packet: + if flags_bits & flags['isResponse']: # it is a response + + # Use device ID and command code to determine which command is being acknowledged: + if devid == deviceID['powerInfo'] and commcode == powerCommandIDs['wake']: + self.notification_ack = "Wake acknowledged" # Acknowledgement after wake command + + elif devid == deviceID['driving'] and commcode == drivingCommands['driveWithHeading']: + self.notification_ack = "Roll command acknowledged" + + elif devid == deviceID['driving'] and commcode == drivingCommands['stabilization']: + self.notification_ack = "Stabilization command acknowledged" + + elif devid == deviceID['userIO'] and commcode == userIOCommandIDs['allLEDs']: + self.notification_ack = "LED/backlight color command acknowledged" + + elif devid == deviceID['driving'] and commcode == drivingCommands["resetHeading"]: + self.notification_ack = "Heading reset command acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["configureCollision"]: + self.notification_ack = "Collision detection configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["configureSensorStream"]: + self.notification_ack = "Sensor stream configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensorMask"]: + self.notification_ack = "Mask configuration acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensor1"]: + self.notification_ack = "Sensor1 acknowledged" + + elif devid == deviceID['sensor'] and commcode == sensorCommands["sensor2"]: + self.notification_ack = "Sensor2 acknowledged" + + elif devid == deviceID['powerInfo'] and commcode == powerCommandIDs['batteryVoltage']: + V_batt = notification_payload[2] + notification_payload[1]*256 + notification_payload[0]*65536 + V_batt /= 100 # Notification gives V_batt in 10mV increments. Divide by 100 to get to volts. + self.notification_ack = "Battery voltage:" + str(V_batt) + "v" + self.sphero_class.v_batt = V_batt + + elif devid == deviceID['systemInfo'] and commcode == SystemInfoCommands['mainApplicationVersion']: + version = '.'.join(str(x) for x in notification_payload) + self.notification_ack = "Firmware version: " + version + self.sphero_class.firmware_version = notification_payload + + else: + self.notification_ack = "Unknown acknowledgement" #print(self.notificationPacket) + print(self.notificationPacket, "===================> Unknown ack packet") + + self.notification_seq = seq + + else: # Not a response packet - therefore, asynchronous notification (e.g. collision detection, etc): + + # Collision detection: + if devid == deviceID['sensor'] and commcode == sensorCommands['collisionDetectedAsync']: + # The first four bytes are data that is still un-parsed. the remaining unsaved bytes are always zeros + _, _, _, _, _, _, axis, _, Y_mag, _, X_mag, *_ = notification_payload + if axis == 1: + dir = "Left/right" + else: + dir = 'Forward/back' + print("Collision detected:") + print("\tAxis: ", dir) + print("\tX_mag: ", X_mag) + print("\tY_mag: ", Y_mag) + + if self.sphero_class.collision_detection_callback is not None: + self.notificationPacket = [] # need to clear packet, in case new notification comes in during callback + self.sphero_class.collision_detection_callback() + + # Sensor response: + elif devid == deviceID['sensor'] and commcode == sensorCommands['sensorResponse']: + # Convert to binary, pad bytes with leading zeros: + val = '' + for byte in notification_payload: + val += format(int(bin(byte)[2:], 2), '#010b')[2:] + + # Break into 32-bit chunks + nums = [] + while(len(val) > 0): + num, val = val[:32], val[32:] # Slice off first 16 bits + nums.append(num) + + # convert from raw bits to float: + nums = [self.bits_to_num(num) for num in nums] + + # Set sensor values as class attributes: + for name, value in zip(self.sphero_class.configured_sensors, nums): + print("Setting sensor at .", name, str(value)) + setattr(self.sphero_class, name, value) + + # Unrecognized packet structure: + else: + self.notification_ack = "Unknown asynchronous notification" #print(self.notificationPacket) + print(str(self.notificationPacket) + " ===================> Unknown async packet") + + self.notificationPacket = [] # Start new payload after this byte \ No newline at end of file diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/karte.jpg b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/karte.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2dfab8ae944dcd069b9970cebe77965f549b21a0 GIT binary patch literal 161695 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 A!~g&Q literal 0 HcmV?d00001 diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/sphero_conf.json b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/sphero_conf.json new file mode 100644 index 0000000..a490b4b --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/sphero_conf.json @@ -0,0 +1,3 @@ +{ + "MAC_ADDRESS": "C6:69:72:CD:BC:6D" +} diff --git a/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/sphero_mini.py b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/sphero_mini.py new file mode 100644 index 0000000..2dd16aa --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/sphero_mini.py @@ -0,0 +1,756 @@ +#!/usr/bin/env python3 +############################################################################ +#%% Imports +import rclpy +from rclpy.node import Node +from sensor_msgs.msg import Imu +import threading +from geometry_msgs.msg import Quaternion, Vector3 +import numpy as np +from sphero_mini_controller.coreROS2 import SpheroMini +import cv2 +import time +import queue +import matplotlib.pyplot as plt +############################################################################ +#%% Blob Detector / Globale Variablen +detector = cv2.SimpleBlobDetector() + +# Setup SimpleBlobDetector parameters. +params = cv2.SimpleBlobDetector_Params() + +# Change thresholds +#params.minThreshold = 5 +#params.maxThreshold = 500 + +#FIlter by Color +params.filterByColor = True +params.blobColor = 255 + +# Filter by Area. +params.filterByArea = True +params.minArea = 10 +params.maxArea = 200 + +# Filter by Circularity +#params.filterByCircularity = True +#params.minCircularity = 0.5 +#params.maxCircularity = 1 + +# Filter by Convexity +#params.filterByConvexity = True +#params.minConvexity = 0.7 +#params.maxConvexity = 1 + +# Filter by Inertia +#params.filterByInertia = True +#params.minInertiaRatio = 0.01 + +# Create a detector with the parameters +detector = cv2.SimpleBlobDetector_create(params) +############################################################################ +#%% Class Video Capture +# Bufferless VideoCapture +# Quelle ? +class VideoCapture: + def __init__(self, name): + self.cap = cv2.VideoCapture(name) + self.q = queue.Queue() + t = threading.Thread(target=self._reader) + t.daemon = True + t.start() + # read frames as soon as they are available, keeping only most recent one + def _reader(self): + while True: + ret, frame = self.cap.read() + #cv2.imshow("Cap", frame) + + if not ret: + break + if not self.q.empty(): + try: + self.q.get_nowait() # discard previous (unprocessed) frame + except queue.Empty: + pass + self.q.put(frame) + + def read(self): + return self.q.get() +############################################################################ +#%% Class Sphero Node +class MyNode(Node): + def __init__(self): + super().__init__("sphero_mini") + + def connect(self): + MAC_ADDRESS = "C6:69:72:CD:BC:6D" + + # Connect: + sphero = SpheroMini(MAC_ADDRESS, verbosity = 4) + # battery voltage + sphero.getBatteryVoltage() + print(f"Bettery voltage: {sphero.v_batt}v") + + # firmware version number + sphero.returnMainApplicationVersion() + print(f"Firmware version: {'.'.join(str(x) for x in sphero.firmware_version)}") + return sphero + + def create_quaternion(self, roll, pitch, yaw): + q = Quaternion() + cy, sy = np.cos(yaw * 0.5), np.sin(yaw * 0.5) + cp, sp = np.cos(pitch * 0.5), np.sin(pitch * 0.5) + cr, sr = np.cos(roll * 0.5), np.sin(roll * 0.5) + + q.w = cr * cp * cy + sr * sp * sy + q.x = sr * cp * cy - cr * sp * sy + q.y = cr * sp * cy + sr * cp * sy + q.z = cr * cp * sy - sr * sp * cy + + return q + + def create_angular_veolocity_vector3(self, groll, gpitch, gyaw): + v = Vector3() + v.x = groll + v.y = gpitch + v.z = float(gyaw) + + return v + + def create_linear_acc_vector3(self, xacc, yacc, zacc): + v = Vector3() + v.x = xacc + v.y = yacc + v.z = zacc + + return v + + def get_sensors_data(self, sphero): + return { + "roll": sphero.IMU_roll, + "pitch": sphero.IMU_pitch, + "yaw": sphero.IMU_yaw, + "groll": sphero.IMU_gyro_x, + "gpitch": sphero.IMU_gyro_y, + "xacc": sphero.IMU_acc_x, + "yacc": sphero.IMU_acc_y, + "zacc": sphero.IMU_acc_z + } + + def publish_imu(self, sensors_values,node): + i = Imu() + + i.header.stamp = node.get_clock().now().to_msg() + + i.orientation = self.create_quaternion( + roll=sensors_values["roll"], + pitch=sensors_values["pitch"], + yaw=sensors_values["yaw"] + ) + i.angular_velocity = self.create_angular_veolocity_vector3( + groll=sensors_values["groll"], + gpitch=sensors_values["gpitch"], + gyaw=0 # We don't have the IMU_gyro_z + ) + i.linear_acceleration = self.create_linear_acc_vector3( + xacc=sensors_values["xacc"], + yacc=sensors_values["yacc"], + zacc=sensors_values["zacc"] + ) + + def get_pos(self, cap, height): + #zeitanfang = time.time() + + success = False + + while(success == False): + try: + img = cap.read() + + gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY ) + keypoints = detector.detect(gray) + + for keyPoint in keypoints: + x = keyPoint.pt[0] + y = keyPoint.pt[1] + success = True + + xTrans, yTrans = self.calc_trans(x,y, height) + except Exception: + continue + + #zeitende = time.time() + #print("Dauer Programmausführung:",(zeitende-zeitanfang)) + #print("Get_Pos: X,Y:", xTrans,yTrans) + + return xTrans,yTrans + + def calc_offset(self, sphero, cap, height): + sphero.roll(100,0) + sphero.wait(1) + sphero.roll(0,0) + sphero.wait(0.5) + + ref = self.get_pos(cap, height) + print("calc_offset: Ref Punkt x,y", ref) + + sphero.wait(1) + sphero.roll(100,180) + sphero.wait(1) + sphero.roll(0,0) + + startpunkt = self.get_pos(cap, height) + print("calc_offset: Startpunkt x,y", startpunkt) + + ref = np.array(ref) + startpunkt = np.array(startpunkt) + + start_ref = ref-startpunkt + + phi = np.arctan2(start_ref[1],start_ref[0]) + phi = np.degrees(phi) + phi = int(-phi) + + print("Phi ohne alles:", phi) + + phi = self.phi_in_range(phi) + + print("Calc_Offset: ", phi) + + return phi + + def calc_av(self,startPos, sollPos, cap, height): + startPos = np.array(startPos) + sollPos = np.array(sollPos) + + pktSpheroKord = sollPos - startPos + + phi = np.arctan2(pktSpheroKord[1], pktSpheroKord[0]) + phi = np.degrees(phi) + + phiZiel = int(phi) + + phiZiel = self.phi_in_range(phiZiel) + + v = 100 + + #print("Calc_AV: a,v", phiZiel, v) + + return phiZiel, v + + def drive_to(self,sphero, targetPos, aOffset, cap, height): + dmin = 20 + pos = self.get_pos(cap, height) + pos = np.array(pos) + + diff = targetPos - pos + + d = np.linalg.norm(diff, ord = 2) + + ar = [] + ar.append(0) + + i = 1 + + while d > dmin: + a,v = self.calc_av(pos,targetPos, cap, height) + + aR = -aOffset - a + ar.append((self.phi_in_range(aR))) + + #Fahrbefehl + if(ar[i] != ar[i-1]): + sphero.roll(v, ar[i]) + sphero.wait(0.05) + else: + sphero.wait(0.05) + + #Aktuelle Pos + pos = self.get_pos(cap, height) + pos = np.array(pos) + + #Abweichung + diff = targetPos - pos + diff = np.array(diff) + d = np.linalg.norm(diff, ord = 2) + + i = i + 1 + + sphero.roll(0,0) + print("Ziel Erreicht") + + return + + def calc_trans(self,x,y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans + + def phi_in_range(self,phi): + while(phi < 0): + phi = phi + 360 + while(phi > 360): + phi = phi - 360 + return phi +############################################################################ +#%% Class Wavefrontplanner +class waveFrontPlanner: + ############################################################################ + # WAVEFRONT ALGORITHM + # Adapted to Python Code By Darin Velarde + # Fri Jan 29 13:56:53 PST 2010 + # from C code from John Palmisano + # (www.societyofrobots.com) + ############################################################################ + + def __init__(self, mapOfWorld, slow=False): + self.__slow = slow + self.__mapOfWorld = mapOfWorld + if str(type(mapOfWorld)).find("numpy") != -1: + #If its a numpy array + self.__height, self.__width = self.__mapOfWorld.shape + else: + self.__height, self.__width = len(self.__mapOfWorld), len(self.__mapOfWorld[0]) + + self.__nothing = 000 + self.__wall = 999 + self.__goal = 1 + self.__path = "PATH" + + self.__finalPath = [] + + #Robot value + self.__robot = 254 + #Robot default Location + self.__robot_x = 0 + self.__robot_y = 0 + + #default goal location + self.__goal_x = 8 + self.__goal_y = 9 + + #temp variables + self.__temp_A = 0 + self.__temp_B = 0 + self.__counter = 0 + self.__steps = 0 #determine how processor intensive the algorithm was + + #when searching for a node with a lower value + self.__minimum_node = 250 + self.__min_node_location = 250 + self.__new_state = 1 + self.__old_state = 1 + self.__reset_min = 250 #above this number is a special (wall or robot) + ########################################################################### + + def setRobotPosition(self, x, y): + """ + Sets the robot's current position + + """ + + self.__robot_x = x + self.__robot_y = y + ########################################################################### + + def setGoalPosition(self, x, y): + """ + Sets the goal position. + + """ + + self.__goal_x = x + self.__goal_y = y + ########################################################################### + + def robotPosition(self): + return (self.__robot_x, self.__robot_y) + ########################################################################### + + def goalPosition(self): + return (self.__goal_x, self.__goal_y) + ########################################################################### + + def run(self, prnt=False): + """ + The entry point for the robot algorithm to use wavefront propagation. + + """ + + path = [] + while self.__mapOfWorld[self.__robot_x][self.__robot_y] != self.__goal: + if self.__steps > 20000: + print ("Cannot find a path.") + return + #find new location to go to + self.__new_state = self.propagateWavefront() + #update location of robot + if self.__new_state == 1: + self.__robot_x -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" % (self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 2: + self.__robot_y += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 3: + self.__robot_x += 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + if self.__new_state == 4: + self.__robot_y -= 1 + if prnt: + print("Move to x=%d y=%d\n\n" %(self.__robot_x, self.__robot_y)) + path.append((self.__robot_x, self.__robot_y)) + self.__old_state = self.__new_state + msg = "Found the goal in %i steps:\n" % self.__steps + msg += "mapOfWorld size= %i %i\n\n" % (self.__height, self.__width) + if prnt: + print(msg) + self.printMap() + return path + ########################################################################### + + def propagateWavefront(self, prnt=False): + self.unpropagate() + #Old robot location was deleted, store new robot location in mapOfWorld + self.__mapOfWorld[self.__robot_x][self.__robot_y] = self.__robot + self.__path = self.__robot + #start location to begin scan at goal location + self.__mapOfWorld[self.__goal_x][self.__goal_y] = self.__goal + counter = 0 + while counter < 200: #allows for recycling until robot is found + x = 0 + y = 0 + #time.sleep(0.00001) + #while the mapOfWorld hasnt been fully scanned + while x < self.__height and y < self.__width: + #if this location is a wall or the goal, just ignore it + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal: + #a full trail to the robot has been located, finished! + minLoc = self.minSurroundingNodeValue(x, y) + if minLoc < self.__reset_min and \ + self.__mapOfWorld[x][y] == self.__robot: + if prnt: + print("Finished Wavefront:\n") + self.printMap() + # Tell the robot to move after this return. + return self.__min_node_location + #record a value in to this node + elif self.__minimum_node != self.__reset_min: + #if this isnt here, 'nothing' will go in the location + self.__mapOfWorld[x][y] = self.__minimum_node + 1 + #go to next node and/or row + y += 1 + if y == self.__width and x != self.__height: + x += 1 + y = 0 + #print self.__robot_x, self.__robot_y + if prnt: + print("Sweep #: %i\n" % (counter + 1)) + self.printMap() + self.__steps += 1 + counter += 1 + return 0 + ########################################################################### + + def unpropagate(self): + """ + clears old path to determine new path + stay within boundary + + """ + + for x in range(0, self.__height): + for y in range(0, self.__width): + if self.__mapOfWorld[x][y] != self.__wall and \ + self.__mapOfWorld[x][y] != self.__goal and \ + self.__mapOfWorld[x][y] != self.__path: + #if this location is a wall or goal, just ignore it + self.__mapOfWorld[x][y] = self.__nothing #clear that space + ########################################################################### + + def minSurroundingNodeValue(self, x, y): + """ + this method looks at a node and returns the lowest value around that + node. + + """ + + #reset minimum + self.__minimum_node = self.__reset_min + #down + if x < self.__height -1: + if self.__mapOfWorld[x + 1][y] < self.__minimum_node and \ + self.__mapOfWorld[x + 1][y] != self.__nothing: + #find the lowest number node, and exclude empty nodes (0's) + self.__minimum_node = self.__mapOfWorld[x + 1][y] + self.__min_node_location = 3 + #up + if x > 0: + if self.__mapOfWorld[x-1][y] < self.__minimum_node and \ + self.__mapOfWorld[x-1][y] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x-1][y] + self.__min_node_location = 1 + #right + if y < self.__width -1: + if self.__mapOfWorld[x][y + 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y + 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y + 1] + self.__min_node_location = 2 + #left + if y > 0: + if self.__mapOfWorld[x][y - 1] < self.__minimum_node and \ + self.__mapOfWorld[x][y - 1] != self.__nothing: + self.__minimum_node = self.__mapOfWorld[x][y-1] + self.__min_node_location = 4 + return self.__minimum_node + ########################################################################### + + def printMap(self): + """ + Prints out the map of this instance of the class. + + """ + + msg = '' + for temp_B in range(0, self.__height): + for temp_A in range(0, self.__width): + if self.__mapOfWorld[temp_B][temp_A] == self.__wall: + msg += "%04s" % "[#]" + elif self.__mapOfWorld[temp_B][temp_A] == self.__robot: + msg += "%04s" % "-" + elif self.__mapOfWorld[temp_B][temp_A] == self.__goal: + msg += "%04s" % "G" + else: + msg += "%04s" % str(self.__mapOfWorld[temp_B][temp_A]) + msg += "\n\n" + msg += "\n\n" + print(msg) + # + if self.__slow == True: + time.sleep(0.05) + + def plotMap(self, img, path,mapWorldScaled,mapWorldOrg): + #Programm Koordinaten + imgTrans = cv2.transpose(img) # X und Y-Achse im Bild tauschen + imgPlot, ax0 = plt.subplots() + ax0.set_title('Programm Koordinaten') + ax0.imshow(imgTrans) + ax0.set_xlabel('[px]') + ax0.set_ylabel('[px]') + ax0.scatter(path[:,0], path[:,1], color='r') + #Karten Array Scaled + imgPlot1, ax1 = plt.subplots() + ax1.set_title('Karten-Array (Vergrößerte Hindernisse e=3)') + ax1.imshow(mapWorldScaled, cmap = 'Greys') + ax1.set_xlabel('[px]') + ax1.set_ylabel('[px]') + #Karten Array Normal + imgPlot2, ax2 = plt.subplots() + ax2.set_title('Karten-Array (Originale Hindernisse)') + ax2.imshow(mapWorldOrg, cmap = 'Greys') + ax2.set_xlabel('[px]') + ax2.set_ylabel('[px]') + #Bild Koordinaten + imgPlot3, ax3 = plt.subplots() + ax3.set_title('Bild Koordinaten') + ax3.set_xlabel('[px]') + ax3.set_ylabel('[px]') + ax3.imshow(img) + ax3.scatter(path[:,1], path[:,0], color='r') + return + + + def getPathWelt(self,path,height,Mapobj): + pathWeltX, pathWeltY = Mapobj.calc_trans_welt(path[:,1], path[:,0], height) + pathEckenX = [] + pathEckenY = [] + + for i,px in enumerate(pathWeltX): + if (i < len(pathWeltX)-1) & (i > 0): + if px != pathWeltX[i+1]: + if pathWeltY[i]!=pathWeltY[i-1]: + pathEckenX.append(px) + pathEckenY.append(pathWeltY[i]) + if pathWeltY[i] != pathWeltY[i+1]: + if pathWeltX[i]!=pathWeltX[i-1]: + pathEckenX.append(px) + pathEckenY.append(pathWeltY[i]) + + pathEckenX.append(pathWeltX[-1]) + pathEckenY.append(pathWeltY[-1]) + + + uebergabePath = [] + for i in range(0,len(pathEckenX)): + uebergabePath.append((pathEckenX[i],pathEckenY[i])) + + print("Ecken: ", uebergabePath) + + return uebergabePath + +############################################################################ +#%% Class Map +class mapCreate: + ############################################################################ + + def create_map(self, scale, cap): + img = cap.read() + + #Karte von Bild + #img = cv2.imread("D:/ros2_ws/src/sphero_mini_controller/sphero_mini_controller/map/map.jpg") + print('Original Dimensions : ',img.shape) + + scale_percent = 100-scale # percent of original size + width = int(img.shape[1] * scale_percent / 100) + height = int(img.shape[0] * scale_percent / 100) + dim = (width, height) + + # resize image + resized = cv2.resize(img, dim, interpolation = cv2.INTER_AREA) + print('Resized Dimensions : ',resized.shape) + + gray = cv2.cvtColor(resized, cv2.COLOR_RGB2GRAY) + mask = cv2.inRange(gray, np.array([200]), np.array([255])) #Parameter hängt von Umgebung ab + + plt.plot(mask) + + mapOfWorld = [[0]*gray.shape[1]]*gray.shape[0] + mask_list= np.ndarray.tolist(mask) + + #Markiere alle leeren Zellen mit 000 und alle Zellen mit Hinderniss mit 999 + for i in range(0,mask.shape[0]): + mapOfWorld[i] = ['999' if j > 200 else '000' for j in mask_list[i]] + + mapOfWorld = np.array(mapOfWorld, dtype = int) + mapOfWorldSized = mapOfWorld.copy() + + e = 3 + #Hindernisse Größer machen + for i in range(0,mapOfWorld.shape[0]): + for j in range(0,mapOfWorld.shape[1]): + for r in range(0,e): + try: + if (mapOfWorldSized[i][j] == 999): + mapOfWorld[i][j+e] = 999 + mapOfWorld[i+e][j] = 999 + mapOfWorld[i-e][j] = 999 + mapOfWorld[i][j-e] = 999 + mapOfWorld[i+e][j+e] = 999 + mapOfWorld[i+e][j-e] = 999 + mapOfWorld[i-e][j+e] = 999 + mapOfWorld[i-e][j-e] = 999 + except Exception: + continue + + return mapOfWorld, mapOfWorldSized + ############################################################################ + + def scale_koord(self, sx, sy, gx, gy,scale): + scale_percent = 100-scale # percent of original size + + sx = int(sx*scale_percent/100) + sy = int(sy*scale_percent/100) + gx = int(gx*scale_percent/100) + gy = int(gy*scale_percent/100) + + return sx,sy,gx,gy + ############################################################################ + + def rescale_koord(self, path,scale): + scale_percent = 100-scale # percent of original size100 + + path = np.array(path) + + for i in range(len(path)): + path[i] = path[i]*100/scale_percent + + return path + ############################################################################ + + def calc_trans_welt(self, x, y, height): + yTrans = height - y + xTrans = x + + return xTrans, yTrans +############################################################################### +#%% Main Funktion +def main(args = None): + #Verbindung herstellen, Node erstellen + try: + rclpy.init(args=args) #Kommunikation Starten + node = MyNode() #Node erstellen + sphero = node.connect() + thread = threading.Thread(target=rclpy.spin, args=(node, ), daemon=True) + thread.start() + + except Exception as e: # rclpy.ROSInterruptException + print("Konnte nicht verbinden") + raise e + + cap = VideoCapture("http://root:root@10.128.41.239:80/mjpg/video.mjpg") + #cap = VideoCapture(4) + Map = mapCreate() + + img = cap.read() + height, width = img.shape[:2] + + scale = 90 + + sphero.setLEDColor(255,0,0) + sphero.wait(1) + sphero.setBackLEDIntensity(0) + sphero.stabilization(True) + + aOffset = node.calc_offset(sphero, cap, height) + + sphero.wait(1) + + while(True): + mapWorldScaled, mapWorldOrg = Map.create_map(scale, cap) + + #Befehle für WaveFrontPlanner/Maperstellung + sy,sx = node.get_pos(cap,height) #Aktuelle Roboter Pos in Welt + sy, sx = node.calc_trans(sy,sx,height) + + gy = int(input("Bitte geben Sie die X-Zielkoordinate im Bild Koordinatensystem ein:")) #X-Koordinate im Weltkoordinaten System + gx = int(input("Bitte geben Sie die Y-Zielkoordinate im Bild Koordinatensystem ein:")) #Y-Koordinate im Weltkordinaten System + + sx,sy,gx,gy = Map.scale_koord(sx,sy,gx,gy,scale) #Kordinaten Tauschen X,Y + + start = time.time() + planner = waveFrontPlanner(mapWorldScaled) + planner.setGoalPosition(gx,gy) + planner.setRobotPosition(sx,sy) + path = planner.run(False) + end = time.time() + print("Took %f seconds to run wavefront simulation" % (end - start)) + + path = Map.rescale_koord(path, scale) + + planner.plotMap(img, path, mapWorldScaled, mapWorldOrg) + + pathWelt = planner.getPathWelt(path, height, Map) + + for p in pathWelt: + node.drive_to(sphero, p, aOffset, cap, height) + sphero.wait(1) + + print("Geschafft") + + if cv2.waitKey(10) & 0xFF == ord('q'): + break + + rclpy.shutdown() + +############################################################################ +#%% Main +if __name__ == '__main__': + main() +############################################################################ diff --git a/ros2_ws/src/sphero_mini_controller/test/test_copyright.py b/ros2_ws/src/sphero_mini_controller/test/test_copyright.py new file mode 100644 index 0000000..97a3919 --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/test/test_copyright.py @@ -0,0 +1,25 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_copyright.main import main +import pytest + + +# Remove the `skip` decorator once the source file(s) have a copyright header +@pytest.mark.skip(reason='No copyright header has been placed in the generated source file.') +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/ros2_ws/src/sphero_mini_controller/test/test_flake8.py b/ros2_ws/src/sphero_mini_controller/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/ros2_ws/src/sphero_mini_controller/test/test_pep257.py b/ros2_ws/src/sphero_mini_controller/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/ros2_ws/src/sphero_mini_controller/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 Open Source Robotics Foundation, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' -- GitLab