From 1da29f2fc373cda6c6ae35cb1e7dd154be91db5b Mon Sep 17 00:00:00 2001 From: Ting-Chia Chiang Date: Wed, 23 Oct 2024 16:55:50 +0200 Subject: [PATCH 1/4] Bug fix: catch errors when a camera is not attached (#584) Bug fix: catch errors when a camera is not attached --- docs/source/physical_robot_core_setup/index.rst | 4 +++- .../5a_physical_robot_remote/main.py | 2 +- modular_robot_physical/pyproject.toml | 2 +- .../modular_robot_physical/remote/_remote.py | 14 +++++++++++++- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/source/physical_robot_core_setup/index.rst b/docs/source/physical_robot_core_setup/index.rst index 012c40f91..4f3bba621 100644 --- a/docs/source/physical_robot_core_setup/index.rst +++ b/docs/source/physical_robot_core_setup/index.rst @@ -39,7 +39,8 @@ On the RPi adjust the config in `/boot/config.txt` or on newer systems `/boot/fi ------------------ Setting up the RPi ------------------ -**Note**: For students in the CI Group, the RPi is already set up. All RPis are flashed with the same image, so the following steps are not necessary. Additionally, there should be an IP address on the head, allowing you to SSH into it. However, be aware that there will always be ongoing development changes in the revolve2-modular-robot_physical and revolve2-robohat packages, so make sure to pip install the latest version in your virtual environment. +**Note**: For students in the CI Group, the RPi is already set up. All RPis are flashed with the same image, so the following steps are not necessary. Additionally, there should be an IP address on the head, allowing you to SSH into it under the *ThymioNet* Wi-Fi. However, be aware that the IP might change from time to time. If you find the IP doesn't work, use the serial connection to log in and obtain the correct IP. For instructions on how to establish a serial connection, please refer to the section below. +Also, note that ongoing development changes will continue in revolve2-modular-robot_physical and revolve2-robohat packages, so make sure to pip install the latest version in your virtual environment. This step is the same for all types of hardware. @@ -135,6 +136,7 @@ Setting up Revolve2 on the robot requires different steps, depending on the hard #. Here, the :code:`Nice=-10` line sets a high priority for the daemon (lower values are higher priority, with -20 being the highest priority). The :code:`-l` option in the :code:`ExecStart` line tells :code:`robot-daemon` to only listen on the localhost interface. The :code:`-n localhost` option ensures that robot-daemon only runs if it can connect to localhost (preventing certain failure cases). #. Enable and start the service: :code:`sudo systemctl daemon-reload` & :code:`sudo systemctl enable robot-daemon` & :code:`sudo systemctl start robot-daemon`. #. Check if it is running properly using: :code:`sudo systemctl status robot-daemon` + #. If it's not running properly, check the logs using: :code:`journalctl -u robot-daemon -e` ^^^^^^^^^^^^^^^^^^^ V1 Additional Steps diff --git a/examples/5_physical_modular_robots/5a_physical_robot_remote/main.py b/examples/5_physical_modular_robots/5a_physical_robot_remote/main.py index b63898ef8..1cb5f521c 100644 --- a/examples/5_physical_modular_robots/5a_physical_robot_remote/main.py +++ b/examples/5_physical_modular_robots/5a_physical_robot_remote/main.py @@ -43,7 +43,7 @@ def make_body() -> ( body.core_v2.right_face.bottom, body.core_v2.right_face.bottom.attachment, ) - """Add a camera sensor to the core.""" + """Here we add a camera sensor to the core. If you don't have a physical camera attached, uncomment this line.""" body.core.add_sensor( CameraSensor(position=Vector3([0, 0, 0]), camera_size=(480, 640)) ) diff --git a/modular_robot_physical/pyproject.toml b/modular_robot_physical/pyproject.toml index da3c1b8d5..f1a8c2fb4 100644 --- a/modular_robot_physical/pyproject.toml +++ b/modular_robot_physical/pyproject.toml @@ -32,7 +32,7 @@ pyrr = "^0.10.3" typed-argparse = "^0.3.1" pycapnp = { version = "^2.0.0b2" } pigpio = { version = "^1.78", optional = true } -revolve2-robohat = { version = "0.6.2", optional = true } +revolve2-robohat = { version = "0.6.3", optional = true } rpi-lgpio = { version = "0.5", optional = true } opencv-python = "^4.10.0.84" # cpnp-stub-generator is disabled because it depends on pycapnp <2.0.0. diff --git a/modular_robot_physical/revolve2/modular_robot_physical/remote/_remote.py b/modular_robot_physical/revolve2/modular_robot_physical/remote/_remote.py index 085b16233..91c45992c 100644 --- a/modular_robot_physical/revolve2/modular_robot_physical/remote/_remote.py +++ b/modular_robot_physical/revolve2/modular_robot_physical/remote/_remote.py @@ -321,10 +321,16 @@ def _get_camera_sensor_state( :param camera_sensor: The sensor in question. :param sensor_readings: The sensor readings. :return: The Sensor state. + :raises RuntimeError: If the camera image is empty. """ if camera_sensor is None: return {} else: + image = sensor_readings.cameraView + if list(image.r) == [0] and list(image.g) == [0] and list(image.b) == [0]: + raise RuntimeError( + "Camera image is emtpy. Are you sure you have attached a camera?" + ) return { UUIDKey(camera_sensor): CameraSensorStateImpl( _capnp_to_camera_view( @@ -343,10 +349,16 @@ def _display_camera_view( :param camera_sensor: The sensor in question. :param sensor_readings: The sensor readings. + :raises RuntimeError: If the camera image is empty. """ if camera_sensor is None: - print("No camera sensor found.") + print("No camera added in the body.") else: + image = sensor_readings.cameraView + if list(image.r) == [0] and list(image.g) == [0] and list(image.b) == [0]: + raise RuntimeError( + "Camera image is emtpy. Are you sure you have attached a camera?" + ) rgb_image = _capnp_to_camera_view( sensor_readings.cameraView, camera_sensor.camera_size ) From 5ea0a48eadbc79930dfc8745b60c6be7a506e073 Mon Sep 17 00:00:00 2001 From: Roy <46519674+royderegt@users.noreply.github.com> Date: Wed, 30 Oct 2024 15:00:49 +0100 Subject: [PATCH 2/4] Fix bug where angle is taken from the same output as module_type (#583) --- .../genotypes/cppnwin/modular_robot/v2/_body_develop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/standards/revolve2/standards/genotypes/cppnwin/modular_robot/v2/_body_develop.py b/standards/revolve2/standards/genotypes/cppnwin/modular_robot/v2/_body_develop.py index c1d748079..f65a92c96 100644 --- a/standards/revolve2/standards/genotypes/cppnwin/modular_robot/v2/_body_develop.py +++ b/standards/revolve2/standards/genotypes/cppnwin/modular_robot/v2/_body_develop.py @@ -117,7 +117,7 @@ def __evaluate_cppn( The output ranges between [0,1] and we have 4 rotations available (0, 90, 180, 270). """ - angle = max(0, int(outputs[0] * 4 - 1e-6)) * (np.pi / 2.0) + angle = max(0, int(outputs[1] * 4 - 1e-6)) * (np.pi / 2.0) return module_type, angle From 0446d67b72c56df5996f977f7cfd7b9f546d6390 Mon Sep 17 00:00:00 2001 From: Ting-Chia Chiang Date: Mon, 11 Nov 2024 14:19:16 +0100 Subject: [PATCH 3/4] Bug fix: the robot daemon should function even when a camera is not attached (#585) --- .../physical_robot_core_setup/index.rst | 2 +- .../_physical_interface.py | 4 ++-- .../v2/_v2_physical_interface.py | 15 ++++++++++---- .../modular_robot_physical/remote/_remote.py | 15 +++++++------- .../robot_daemon/_robo_server_impl.py | 20 ++++++++++++------- 5 files changed, 35 insertions(+), 21 deletions(-) diff --git a/docs/source/physical_robot_core_setup/index.rst b/docs/source/physical_robot_core_setup/index.rst index 4f3bba621..77e74c34d 100644 --- a/docs/source/physical_robot_core_setup/index.rst +++ b/docs/source/physical_robot_core_setup/index.rst @@ -39,7 +39,7 @@ On the RPi adjust the config in `/boot/config.txt` or on newer systems `/boot/fi ------------------ Setting up the RPi ------------------ -**Note**: For students in the CI Group, the RPi is already set up. All RPis are flashed with the same image, so the following steps are not necessary. Additionally, there should be an IP address on the head, allowing you to SSH into it under the *ThymioNet* Wi-Fi. However, be aware that the IP might change from time to time. If you find the IP doesn't work, use the serial connection to log in and obtain the correct IP. For instructions on how to establish a serial connection, please refer to the section below. +**Note**: For students in the CI Group, the RPi is already set up. If the heads are labeled as `flashed`, it means they are already flashed with the setup image, so the following steps are unnecessary. Additionally, the flashed heads are already connected to the *ThymioNet* Wi-Fi. However, the IP address on the head changes from time to time, so you should use the serial connection to log in and obtain the correct IP address. For instructions on how to establish a serial connection, please refer to the section below. Also, note that ongoing development changes will continue in revolve2-modular-robot_physical and revolve2-robohat packages, so make sure to pip install the latest version in your virtual environment. This step is the same for all types of hardware. diff --git a/modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/_physical_interface.py b/modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/_physical_interface.py index bbc9f5d16..937cda4ed 100644 --- a/modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/_physical_interface.py +++ b/modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/_physical_interface.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Sequence +from typing import Optional, Sequence import numpy as np from numpy.typing import NDArray @@ -79,7 +79,7 @@ def get_imu_specific_force(self) -> Vector3: """ @abstractmethod - def get_camera_view(self) -> NDArray[np.uint8]: + def get_camera_view(self) -> Optional[NDArray[np.uint8]]: """ Get the current view from the camera. diff --git a/modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/v2/_v2_physical_interface.py b/modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/v2/_v2_physical_interface.py index 7d34ddec7..c462e6c28 100644 --- a/modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/v2/_v2_physical_interface.py +++ b/modular_robot_physical/revolve2/modular_robot_physical/physical_interfaces/v2/_v2_physical_interface.py @@ -1,5 +1,5 @@ import math -from typing import Sequence +from typing import Optional, Sequence import numpy as np from numpy.typing import NDArray @@ -178,11 +178,18 @@ def get_imu_specific_force(self) -> Vector3: raise RuntimeError("Could not get IMU acceleration reading!") return Vector3(accel) - def get_camera_view(self) -> NDArray[np.uint8]: + def get_camera_view(self) -> Optional[NDArray[np.uint8]]: """ Get the current view from the camera. :returns: An image captured from robohatlib. """ - image = self.cam.get_capture_array().astype(np.uint8) - return image + try: + image = self.cam.get_capture_array() + if image is None: + print("No image captured (camera may not be available).") + return None + return image.astype(np.uint8) + except RuntimeError as e: + print(f"Runtime error encountered: {e}") + return None diff --git a/modular_robot_physical/revolve2/modular_robot_physical/remote/_remote.py b/modular_robot_physical/revolve2/modular_robot_physical/remote/_remote.py index 91c45992c..1af6be7d4 100644 --- a/modular_robot_physical/revolve2/modular_robot_physical/remote/_remote.py +++ b/modular_robot_physical/revolve2/modular_robot_physical/remote/_remote.py @@ -327,9 +327,10 @@ def _get_camera_sensor_state( return {} else: image = sensor_readings.cameraView - if list(image.r) == [0] and list(image.g) == [0] and list(image.b) == [0]: + if len(image.r) == 0 and len(image.g) == 0 and len(image.b) == 0: raise RuntimeError( - "Camera image is emtpy. Are you sure you have attached a camera?" + "Camera image is empty. Are you sure you have attached a camera? " + "If you don't want to get the camera state, don't add it to the body." ) return { UUIDKey(camera_sensor): CameraSensorStateImpl( @@ -352,13 +353,13 @@ def _display_camera_view( :raises RuntimeError: If the camera image is empty. """ if camera_sensor is None: - print("No camera added in the body.") + raise RuntimeError( + "Can't display camera because there is no camera added in the body" + ) else: image = sensor_readings.cameraView - if list(image.r) == [0] and list(image.g) == [0] and list(image.b) == [0]: - raise RuntimeError( - "Camera image is emtpy. Are you sure you have attached a camera?" - ) + if len(image.r) == 0 and len(image.g) == 0 and len(image.b) == 0: + raise RuntimeError("Image is emtpy so nothing can be displayed") rgb_image = _capnp_to_camera_view( sensor_readings.cameraView, camera_sensor.camera_size ) diff --git a/modular_robot_physical/revolve2/modular_robot_physical/robot_daemon/_robo_server_impl.py b/modular_robot_physical/revolve2/modular_robot_physical/robot_daemon/_robo_server_impl.py index 30824cb2c..0f33f6774 100644 --- a/modular_robot_physical/revolve2/modular_robot_physical/robot_daemon/_robo_server_impl.py +++ b/modular_robot_physical/revolve2/modular_robot_physical/robot_daemon/_robo_server_impl.py @@ -256,7 +256,6 @@ def _get_sensor_readings( pins_readings.append(value) battery = self._physical_interface.get_battery_level() - imu_orientation = self._physical_interface.get_imu_orientation() imu_specific_force = self._physical_interface.get_imu_specific_force() imu_angular_rate = self._physical_interface.get_imu_angular_rate() @@ -284,7 +283,7 @@ def _vector3_to_capnp(vector: Vector3) -> capnpVector3: ) @staticmethod - def _camera_view_to_capnp(image: NDArray[np.uint8]) -> capnpImage: + def _camera_view_to_capnp(image: NDArray[np.uint8] | None) -> capnpImage: """ Convert an image as an NDArray into an capnp compatible Image. @@ -294,8 +293,15 @@ def _camera_view_to_capnp(image: NDArray[np.uint8]) -> capnpImage: :return: The capnp Image object. """ # Convert each channel to a list of Int32 for Cap'n Proto - return robot_daemon_protocol_capnp.Image( - r=image[:, :, 0].astype(np.int32).flatten().tolist(), - g=image[:, :, 1].astype(np.int32).flatten().tolist(), - b=image[:, :, 2].astype(np.int32).flatten().tolist(), - ) + if image is None: + return robot_daemon_protocol_capnp.Image( + r=[], + g=[], + b=[], + ) + else: + return robot_daemon_protocol_capnp.Image( + r=image[:, :, 0].astype(np.int32).flatten().tolist(), + g=image[:, :, 1].astype(np.int32).flatten().tolist(), + b=image[:, :, 2].astype(np.int32).flatten().tolist(), + ) From 6065e200611a938027a73f1adbb9b4d811a9e717 Mon Sep 17 00:00:00 2001 From: Ting-Chia Chiang Date: Tue, 12 Nov 2024 12:44:56 +0100 Subject: [PATCH 4/4] Bump version to 1.2.3 (#586) --- docs/source/physical_robot_core_setup/index.rst | 4 ++-- experimentation/pyproject.toml | 2 +- modular_robot/pyproject.toml | 2 +- modular_robot_physical/pyproject.toml | 4 ++-- modular_robot_simulation/pyproject.toml | 6 +++--- simulation/pyproject.toml | 2 +- simulators/mujoco_simulator/pyproject.toml | 4 ++-- standards/pyproject.toml | 4 ++-- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/source/physical_robot_core_setup/index.rst b/docs/source/physical_robot_core_setup/index.rst index 77e74c34d..0a5736ba5 100644 --- a/docs/source/physical_robot_core_setup/index.rst +++ b/docs/source/physical_robot_core_setup/index.rst @@ -109,8 +109,8 @@ Setting up Revolve2 on the robot requires different steps, depending on the hard * V1: :code:`pip install "revolve2-modular_robot_physical[botv1] @ git+https://github.com/ci-group/revolve2.git@#subdirectory=modular_robot_physical"`. * V2: :code:`pip install "revolve2-modular_robot_physical[botv2] @ git+https://github.com/ci-group/revolve2.git@#subdirectory=modular_robot_physical"`. - For example, if you want to install the version tagged as 1.2.2, the command would be: - :code:`pip install "revolve2-modular_robot_physical[botv2] @ git+https://github.com/ci-group/revolve2.git@1.2.2#subdirectory=modular_robot_physical"` + For example, if you want to install the version tagged as 1.2.3, the command would be: + :code:`pip install "revolve2-modular_robot_physical[botv2] @ git+https://github.com/ci-group/revolve2.git@1.2.3#subdirectory=modular_robot_physical"` #. Set up the Revolve2 physical robot daemon: #. Create a systemd service file: :code:`sudo nano /etc/systemd/system/robot-daemon.service` diff --git a/experimentation/pyproject.toml b/experimentation/pyproject.toml index 32bf2e99d..95fdeb2eb 100644 --- a/experimentation/pyproject.toml +++ b/experimentation/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "revolve2-experimentation" -version = "1.2.2" +version = "1.2.3" description = "Revolve2: Tools for experimentation." readme = "../README.md" authors = [ diff --git a/modular_robot/pyproject.toml b/modular_robot/pyproject.toml index 0e61199d2..bd78aa0b1 100644 --- a/modular_robot/pyproject.toml +++ b/modular_robot/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "revolve2-modular-robot" -version = "1.2.2" +version = "1.2.3" description = "Revolve2: Everything for defining modular robots." readme = "../README.md" authors = [ diff --git a/modular_robot_physical/pyproject.toml b/modular_robot_physical/pyproject.toml index f1a8c2fb4..2229334c4 100644 --- a/modular_robot_physical/pyproject.toml +++ b/modular_robot_physical/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "revolve2-modular-robot-physical" -version = "1.2.2" +version = "1.2.3" description = "Revolve2: Everything for physical modular robot control. This package is intended to be installed on the modular robot hardware." readme = "../README.md" authors = [ @@ -27,7 +27,7 @@ packages = [{ include = "revolve2" }] [tool.poetry.dependencies] python = "^3.10,<3.12" -revolve2-modular-robot = "1.2.2" +revolve2-modular-robot = "1.2.3" pyrr = "^0.10.3" typed-argparse = "^0.3.1" pycapnp = { version = "^2.0.0b2" } diff --git a/modular_robot_simulation/pyproject.toml b/modular_robot_simulation/pyproject.toml index 1cc03c4b8..de4b726d3 100644 --- a/modular_robot_simulation/pyproject.toml +++ b/modular_robot_simulation/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "revolve2-modular-robot-simulation" -version = "1.2.2" +version = "1.2.3" description = "Revolve2: Functionality to define scenes with modular robots in a terrain and simulate them." readme = "../README.md" authors = [ @@ -27,8 +27,8 @@ packages = [{ include = "revolve2" }] [tool.poetry.dependencies] python = "^3.10,<3.12" -revolve2-modular-robot = "1.2.2" -revolve2-simulation = "1.2.2" +revolve2-modular-robot = "1.2.3" +revolve2-simulation = "1.2.3" [tool.poetry.extras] dev = [] diff --git a/simulation/pyproject.toml b/simulation/pyproject.toml index 5400f337c..e8a72bf47 100644 --- a/simulation/pyproject.toml +++ b/simulation/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "revolve2-simulation" -version = "1.2.2" +version = "1.2.3" description = "Revolve2: Physics simulation abstraction layer." readme = "../README.md" authors = [ diff --git a/simulators/mujoco_simulator/pyproject.toml b/simulators/mujoco_simulator/pyproject.toml index b32fca2c4..f29b30dbc 100644 --- a/simulators/mujoco_simulator/pyproject.toml +++ b/simulators/mujoco_simulator/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "revolve2-mujoco-simulator" -version = "1.2.2" +version = "1.2.3" description = "Revolve2: MuJoCo simulator." readme = "../../README.md" authors = [ @@ -28,7 +28,7 @@ packages = [{ include = "revolve2" }] [tool.poetry.dependencies] python = "^3.10,<3.12" -revolve2-simulation = "1.2.2" +revolve2-simulation = "1.2.3" mujoco-python-viewer = "^0.1.3" mujoco = "^2.2.0" dm-control = "^1.0.3" diff --git a/standards/pyproject.toml b/standards/pyproject.toml index b66ac907b..7b137e0c5 100644 --- a/standards/pyproject.toml +++ b/standards/pyproject.toml @@ -9,7 +9,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "revolve2-standards" -version = "1.2.2" +version = "1.2.3" description = "Revolve2: Standard tools, parameters, terrains, robots and more for simulations and experiments." readme = "../README.md" authors = [ @@ -38,7 +38,7 @@ script = "revolve2/standards/morphological_novelty_metric/_build_cmodule.py" [tool.poetry.dependencies] python = "^3.10,<3.12" -revolve2-modular-robot-simulation = "1.2.2" +revolve2-modular-robot-simulation = "1.2.3" noise = "^1.2.2" multineat = "^0.12" sqlalchemy = "^2.0.0"