diff --git a/.gitignore b/.gitignore index 3769a4ab..5c9f39c1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ # Visual Studio Code files .vscode + +# PyCache +*.pyc diff --git a/andino_description/CMakeLists.txt b/andino_description/CMakeLists.txt index 16b4becc..0eacb21b 100644 --- a/andino_description/CMakeLists.txt +++ b/andino_description/CMakeLists.txt @@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.8) project(andino_description) find_package(ament_cmake REQUIRED) +find_package(ament_cmake_python REQUIRED) install( DIRECTORY @@ -14,4 +15,19 @@ install( share/${PROJECT_NAME}/ ) +if(BUILD_TESTING) + find_package(ament_cmake_pytest REQUIRED) + set(_pytest_tests + test/test_xacro_processing.py + ) + foreach(_test_path ${_pytest_tests}) + get_filename_component(_test_name ${_test_path} NAME_WE) + ament_add_pytest_test(${_test_name} ${_test_path} + APPEND_ENV PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR} + TIMEOUT 60 + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) + endforeach() +endif() + ament_package() diff --git a/andino_description/README.md b/andino_description/README.md index 73d299e2..8e59ab99 100644 --- a/andino_description/README.md +++ b/andino_description/README.md @@ -5,6 +5,14 @@ This package holds the urdf description of the robot. +In the `urdf` folder you have the URDF files that contain the description of the robot, divided in different modules and merged into `andino.urdf.xacro` file. + +## Configuration + +In case you want to change the physical properties of some of the components of the robot, you can do it modifying the default YAML files inside the `config/andino` folder. + +You can even add your own configuration files in another directory in the `config` folder, and pass this directory to the main file using the `yaml_config_dir` xacro argument on the launch files. + ## Launch Files For launching robot state publisher for filling up static tf information and serving the description of the robot. Typically used during robot bringup. @@ -16,5 +24,3 @@ For launching the robot state publisher and providing some visualization with rv ``` ros2 launch andino_description view_andino.launch.py ``` - - diff --git a/andino_description/config/andino/hardware.yaml b/andino_description/config/andino/hardware.yaml new file mode 100644 index 00000000..cc162915 --- /dev/null +++ b/andino_description/config/andino/hardware.yaml @@ -0,0 +1,6 @@ +left_wheel_name: left_wheel_joint +right_wheel_name: right_wheel_joint +serial_device: /dev/ttyUSB_ARDUINO +baud_rate: 57600 +timeout: 1000 +enc_ticks_per_rev: 585 diff --git a/andino_description/launch/andino_description.launch.py b/andino_description/launch/andino_description.launch.py index 78122fa2..396059d7 100644 --- a/andino_description/launch/andino_description.launch.py +++ b/andino_description/launch/andino_description.launch.py @@ -31,7 +31,7 @@ import os from ament_index_python.packages import get_package_share_directory -from launch import LaunchDescription +from launch import LaunchDescription, LaunchContext from launch.actions import DeclareLaunchArgument from launch.conditions import IfCondition from launch.substitutions import LaunchConfiguration @@ -49,7 +49,8 @@ def generate_launch_description(): pkg_andino_description = get_package_share_directory('andino_description') # Obtain urdf from xacro files. - doc = xacro.process_file(os.path.join(pkg_andino_description, 'urdf', 'andino.urdf.xacro')) + arguments = {'yaml_config_dir': os.path.join(pkg_andino_description, 'config', 'andino')} + doc = xacro.process_file(os.path.join(pkg_andino_description, 'urdf', 'andino.urdf.xacro'), mappings = arguments) robot_desc = doc.toprettyxml(indent=' ') params = {'robot_description': robot_desc, 'publish_frequency': 30.0} diff --git a/andino_description/launch/view_andino.launch.py b/andino_description/launch/view_andino.launch.py index 33f93169..396b8a16 100644 --- a/andino_description/launch/view_andino.launch.py +++ b/andino_description/launch/view_andino.launch.py @@ -53,7 +53,8 @@ def generate_launch_description(): pkg_andino_description = get_package_share_directory('andino_description') # Obtain urdf from xacro files. - doc = xacro.process_file(os.path.join(pkg_andino_description, 'urdf', 'andino.urdf.xacro')) + arguments = {'yaml_config_dir': os.path.join(pkg_andino_description, 'config', 'andino')} + doc = xacro.process_file(os.path.join(pkg_andino_description, 'urdf', 'andino.urdf.xacro'), mappings = arguments) robot_desc = doc.toprettyxml(indent=' ') params = {'robot_description': robot_desc, 'publish_frequency': 30.0} diff --git a/andino_description/package.xml b/andino_description/package.xml index 5d630556..20d4cac2 100644 --- a/andino_description/package.xml +++ b/andino_description/package.xml @@ -9,6 +9,7 @@ BSD Clause 3 ament_cmake + ament_cmake_python joint_state_publisher_gui robot_state_publisher @@ -16,6 +17,9 @@ rviz2 xacro + ament_cmake_pytest + ament_index_python + ament_cmake diff --git a/andino_description/test/test_xacro_processing.py b/andino_description/test/test_xacro_processing.py new file mode 100644 index 00000000..6b3fe7fc --- /dev/null +++ b/andino_description/test/test_xacro_processing.py @@ -0,0 +1,45 @@ +# BSD 3-Clause License + +# Copyright (c) 2024, Ekumen Inc. +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: + +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. + +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. + +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import os +import pytest +import xacro +from ament_index_python.packages import get_package_share_directory + +def test_xacro_processing(): + """Test main xacro file (andino.urdf.xacro) processing""" + # Get the file path. + xacro_file_path = os.path.join(get_package_share_directory("andino_description"), 'urdf', 'andino.urdf.xacro') + + # Test xacro processing. + try: + xacro.process_file(xacro_file_path) + except Exception as e: + pytest.fail(f"Xacro processing failed: {e}") diff --git a/andino_description/urdf/andino.urdf.xacro b/andino_description/urdf/andino.urdf.xacro index 099b5716..1db4ed31 100644 --- a/andino_description/urdf/andino.urdf.xacro +++ b/andino_description/urdf/andino.urdf.xacro @@ -30,6 +30,10 @@ + + + + @@ -89,6 +93,7 @@ + diff --git a/andino_description/urdf/include/andino_control.urdf.xacro b/andino_description/urdf/include/andino_control.urdf.xacro index 8dc8949a..9aee0f29 100644 --- a/andino_description/urdf/include/andino_control.urdf.xacro +++ b/andino_description/urdf/include/andino_control.urdf.xacro @@ -1,34 +1,43 @@ - - - - andino_base/DiffDriveAndino - - left_wheel_joint - right_wheel_joint - /dev/ttyUSB_ARDUINO - 57600 - 1000 - 585 - - - - -5 - 5 - - - - - - - -5 - 5 - - - - - + + + + + + + + andino_base/DiffDriveAndino + ${hardware_props['left_wheel_name']} + ${hardware_props['right_wheel_name']} + ${hardware_props['serial_device']} + ${hardware_props['baud_rate']} + ${hardware_props['timeout']} + ${hardware_props['enc_ticks_per_rev']} + + + + -5 + 5 + + + + + + + -5 + 5 + + + + + + + diff --git a/andino_firmware/README.md b/andino_firmware/README.md index f4337c8b..cb1e2d8e 100644 --- a/andino_firmware/README.md +++ b/andino_firmware/README.md @@ -9,6 +9,9 @@ Check `encoder_driver.h` and `motor_driver.h` files to check the expected pins f ## Installation ### Arduino +In Arduino IDE, go to `tools->Manage Libraries ...` and install: +- "Adafruit BNO055" + Verify and Upload `andino_firmware.ino` to your arduino board. ### PlatformIO diff --git a/andino_firmware/platformio.ini b/andino_firmware/platformio.ini index b63cf7ae..6283cef5 100644 --- a/andino_firmware/platformio.ini +++ b/andino_firmware/platformio.ini @@ -22,6 +22,12 @@ platform = atmelavr framework = arduino monitor_speed = 57600 test_ignore = desktop/* +lib_deps = + Wire + SPI + adafruit/Adafruit BNO055 + adafruit/Adafruit BusIO + adafruit/Adafruit Unified Sensor ; Base configuration for desktop platforms (for unit testing). [base_desktop] diff --git a/andino_firmware/src/app.cpp b/andino_firmware/src/app.cpp index f9577468..32c45ce7 100644 --- a/andino_firmware/src/app.cpp +++ b/andino_firmware/src/app.cpp @@ -64,7 +64,11 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "app.h" +#include +#include #include +#include +#include #include "commands.h" #include "constants.h" @@ -115,6 +119,10 @@ unsigned long App::last_pid_computation_{0}; unsigned long App::last_set_motors_speed_cmd_{0}; +bool App::is_imu_connected{false}; + +Adafruit_BNO055 App::bno055_imu_{/*sensorID=*/55, BNO055_ADDRESS_A, &Wire}; + void App::setup() { // Required by Arduino libraries to work. init(); @@ -142,6 +150,14 @@ void App::setup() { shell_.register_command(Commands::kSetMotorsSpeed, cmd_set_motors_speed_cb); shell_.register_command(Commands::kSetMotorsPwm, cmd_set_motors_pwm_cb); shell_.register_command(Commands::kSetPidsTuningGains, cmd_set_pid_tuning_gains_cb); + shell_.register_command(Commands::kGetIsImuConnected, cmd_get_is_imu_connected_cb); + shell_.register_command(Commands::kReadEncodersAndImu, cmd_read_encoders_and_imu_cb); + + // Initialize IMU sensor. + if (bno055_imu_.begin()) { + bno055_imu_.setExtCrystalUse(true); + is_imu_connected = true; + } } void App::loop() { @@ -171,10 +187,10 @@ void App::adjust_motors_speed() { int right_motor_speed = 0; left_pid_controller_.compute(left_encoder_.read(), left_motor_speed); right_pid_controller_.compute(right_encoder_.read(), right_motor_speed); - if (left_pid_controller_.enabled()){ + if (left_pid_controller_.enabled()) { left_motor_.set_speed(left_motor_speed); } - if (right_pid_controller_.enabled()){ + if (right_pid_controller_.enabled()) { right_motor_.set_speed(right_motor_speed); } } @@ -299,4 +315,47 @@ void App::cmd_set_pid_tuning_gains_cb(int argc, char** argv) { Serial.println("OK"); } +void App::cmd_get_is_imu_connected_cb(int, char**) { Serial.println(is_imu_connected); } + +void App::cmd_read_encoders_and_imu_cb(int, char**) { + Serial.print(left_encoder_.read()); + Serial.print(" "); + Serial.print(right_encoder_.read()); + Serial.print(" "); + + // Retrieve absolute orientation (quaternion). See + // https://learn.adafruit.com/adafruit-bno055-absolute-orientation-sensor/overview for further + // information. + imu::Quaternion orientation = bno055_imu_.getQuat(); + Serial.print(orientation.x(), 4); + Serial.print(" "); + Serial.print(orientation.y(), 4); + Serial.print(" "); + Serial.print(orientation.z(), 4); + Serial.print(" "); + Serial.print(orientation.w(), 4); + Serial.print(" "); + + // Retrieve angular velocity (rad/s). See + // https://learn.adafruit.com/adafruit-bno055-absolute-orientation-sensor/overview for further + // information. + imu::Vector<3> angular_velocity = bno055_imu_.getVector(Adafruit_BNO055::VECTOR_GYROSCOPE); + Serial.print(angular_velocity.x()); + Serial.print(" "); + Serial.print(angular_velocity.y()); + Serial.print(" "); + Serial.print(angular_velocity.z()); + Serial.print(" "); + + // Retrieve linear acceleration (m/s^2). See + // https://learn.adafruit.com/adafruit-bno055-absolute-orientation-sensor/overview for further + // information. + imu::Vector<3> linear_acceleration = bno055_imu_.getVector(Adafruit_BNO055::VECTOR_LINEARACCEL); + Serial.print(linear_acceleration.x()); + Serial.print(" "); + Serial.print(linear_acceleration.y()); + Serial.print(" "); + Serial.print(linear_acceleration.z()); +} + } // namespace andino diff --git a/andino_firmware/src/app.h b/andino_firmware/src/app.h index 937c7827..0bcbef8f 100644 --- a/andino_firmware/src/app.h +++ b/andino_firmware/src/app.h @@ -29,6 +29,8 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #pragma once +#include + #include "digital_out_arduino.h" #include "encoder.h" #include "interrupt_in_arduino.h" @@ -83,6 +85,12 @@ class App { /// Callback method for the `Commands::kSetPidsTuningGains` command. static void cmd_set_pid_tuning_gains_cb(int argc, char** argv); + /// Callback method for the `Commands::kGetIsImuConnected` command. + static void cmd_get_is_imu_connected_cb(int argc, char** argv); + + /// Callback method for the `Commands::kReadEncodersAndImu` command. + static void cmd_read_encoders_and_imu_cb(int argc, char** argv); + /// Serial stream. static SerialStreamArduino serial_stream_; @@ -115,11 +123,17 @@ class App { static Pid left_pid_controller_; static Pid right_pid_controller_; + /// Adafruit BNO055 IMU sensor. + static Adafruit_BNO055 bno055_imu_; + /// Tracks the last time the PID computation was made. static unsigned long last_pid_computation_; /// Tracks the last time a `Commands::kSetMotorsSpeed` command was received. static unsigned long last_set_motors_speed_cmd_; + + /// Tracks whether there is an IMU sensor connected. + static bool is_imu_connected; }; } // namespace andino diff --git a/andino_firmware/src/commands.h b/andino_firmware/src/commands.h index ff3e5ac5..8136f2e3 100644 --- a/andino_firmware/src/commands.h +++ b/andino_firmware/src/commands.h @@ -82,6 +82,10 @@ struct Commands { static constexpr const char* kSetMotorsPwm{"o"}; /// @brief Sets the PIDs tuning gains [format: "kp:kd:ki:ko"]. static constexpr const char* kSetPidsTuningGains{"u"}; + /// @brief Gets whether there is an IMU sensor connected. + static constexpr const char* kGetIsImuConnected{"h"}; + /// @brief Reads the encoders tick count values and IMU sensor data. + static constexpr const char* kReadEncodersAndImu{"i"}; }; } // namespace andino diff --git a/andino_firmware/src/hw.h b/andino_firmware/src/hw.h index 0b50b2d9..850525ac 100644 --- a/andino_firmware/src/hw.h +++ b/andino_firmware/src/hw.h @@ -38,10 +38,10 @@ struct Hw { /// @brief Left encoder channel B pin. Connected to PD3 (digital pin 3). static constexpr int kLeftEncoderChannelBGpioPin{3}; - /// @brief Right encoder channel A pin. Connected to PC4 (digital pin 18, analog pin A4). - static constexpr int kRightEncoderChannelAGpioPin{18}; - /// @brief Right encoder channel B pin. Connected to PC5 (digital pin 19, analog pin A5). - static constexpr int kRightEncoderChannelBGpioPin{19}; + /// @brief Right encoder channel A pin. Connected to PC2 (digital pin 16, analog pin A2). + static constexpr int kRightEncoderChannelAGpioPin{16}; + /// @brief Right encoder channel B pin. Connected to PC3 (digital pin 17, analog pin A3). + static constexpr int kRightEncoderChannelBGpioPin{17}; /// @brief Left motor driver backward pin. Connected to PD6 (digital pin 6). static constexpr int kLeftMotorBackwardGpioPin{6}; @@ -60,6 +60,11 @@ struct Hw { /// @note The enable input of the L298N motor driver may be directly jumped to 5V if the board has /// a jumper to do so. static constexpr int kRightMotorEnableGpioPin{12}; + + /// @brief IMU sensor I2C SCL pin. Connected to PC5 (digital pin 19, analog pin A5). + static constexpr int kImuI2cSclPin{19}; + /// @brief IMU sensor I2C SDA pin. Connected to PC4 (digital pin 18, analog pin A4). + static constexpr int kImuI2cSdaPin{18}; }; } // namespace andino diff --git a/andino_firmware/test/desktop/test_shell/shell_test.cpp b/andino_firmware/test/desktop/test_shell/shell_test.cpp index 71789d7e..1216419d 100644 --- a/andino_firmware/test/desktop/test_shell/shell_test.cpp +++ b/andino_firmware/test/desktop/test_shell/shell_test.cpp @@ -29,6 +29,9 @@ // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "shell.h" +#include +#include + #include #include @@ -96,9 +99,7 @@ class ShellTest : public testing::Test { static void save_arguments(int argc, char** argv) { argc_ = argc; - for (int i = 0; i < argc; i++) { - strcpy(argv_[i], argv[i]); - } + argv_.assign(argv, argv + argc); } static constexpr const char* kCommand1{"a"}; @@ -107,15 +108,15 @@ class ShellTest : public testing::Test { static int called_callback_; static int argc_; - static char argv_[5][10]; + static std::vector argv_; andino::Shell shell; MockSerialStream serial_stream_; }; -int ShellTest::called_callback_ = -1; -int ShellTest::argc_ = 0; -char ShellTest::argv_[5][10] = {'\0'}; +int ShellTest::called_callback_{-1}; +int ShellTest::argc_{0}; +std::vector ShellTest::argv_; TEST_F(ShellTest, ProcessInputEmpty) { EXPECT_CALL(serial_stream_, available()).Times(1).WillOnce(Return(0)); @@ -124,170 +125,164 @@ TEST_F(ShellTest, ProcessInputEmpty) { } TEST_F(ShellTest, ProcessInputMessageSingleCharacterCommandSingleArg) { - const char* input_message = "a\r"; - const char* expected_argv[] = {"a"}; + const std::string input_message{"a\r"}; + const std::vector expected_argv{"a"}; - int available_call_count = strlen(input_message) + 1; + int available_call_count = input_message.size() + 1; EXPECT_CALL(serial_stream_, available()).Times(available_call_count); ON_CALL(serial_stream_, available()) .WillByDefault( testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); int input_index = 0; - EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + EXPECT_CALL(serial_stream_, read()).Times(input_message.size()); ON_CALL(serial_stream_, read()) .WillByDefault(testing::Invoke( - [input_message, &input_index]() -> int { return input_message[input_index++]; })); + [input_message, &input_index]() -> int { return input_message.at(input_index++); })); shell.process_input(); ASSERT_EQ(called_callback_, 1); ASSERT_EQ(argc_, 1); - EXPECT_STREQ(argv_[0], expected_argv[0]); + EXPECT_THAT(argv_, ::testing::ElementsAreArray(expected_argv)); } TEST_F(ShellTest, ProcessInputMessageUnknownCommand) { - const char* input_message = "z\r"; - const char* expected_argv[] = {"z"}; + const std::string input_message{"z\r"}; + const std::vector expected_argv{"z"}; - int available_call_count = strlen(input_message) + 1; + int available_call_count = input_message.size() + 1; EXPECT_CALL(serial_stream_, available()).Times(available_call_count); ON_CALL(serial_stream_, available()) .WillByDefault( testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); int input_index = 0; - EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + EXPECT_CALL(serial_stream_, read()).Times(input_message.size()); ON_CALL(serial_stream_, read()) .WillByDefault(testing::Invoke( - [input_message, &input_index]() -> int { return input_message[input_index++]; })); + [input_message, &input_index]() -> int { return input_message.at(input_index++); })); shell.process_input(); ASSERT_EQ(called_callback_, 0); ASSERT_EQ(argc_, 1); - EXPECT_STREQ(argv_[0], expected_argv[0]); + EXPECT_THAT(argv_, ::testing::ElementsAreArray(expected_argv)); } TEST_F(ShellTest, ProcessInputMessageTwoCharacterCommandSingleArg) { - const char* input_message = "ab\r"; - const char* expected_argv[] = {"ab"}; + const std::string input_message{"ab\r"}; + const std::vector expected_argv{"ab"}; - int available_call_count = strlen(input_message) + 1; + int available_call_count = input_message.size() + 1; EXPECT_CALL(serial_stream_, available()).Times(available_call_count); ON_CALL(serial_stream_, available()) .WillByDefault( testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); int input_index = 0; - EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + EXPECT_CALL(serial_stream_, read()).Times(input_message.size()); ON_CALL(serial_stream_, read()) .WillByDefault(testing::Invoke( - [input_message, &input_index]() -> int { return input_message[input_index++]; })); + [input_message, &input_index]() -> int { return input_message.at(input_index++); })); shell.process_input(); ASSERT_EQ(called_callback_, 2); ASSERT_EQ(argc_, 1); - EXPECT_STREQ(argv_[0], expected_argv[0]); + EXPECT_EQ(argv_.at(0), expected_argv.at(0)); } TEST_F(ShellTest, ProcessInputMessageThreeCharacterCommandSingleArg) { - const char* input_message = "cde\r"; - const char* expected_argv[] = {"cde"}; + const std::string input_message{"cde\r"}; + const std::vector expected_argv{"cde"}; - int available_call_count = strlen(input_message) + 1; + int available_call_count = input_message.size() + 1; EXPECT_CALL(serial_stream_, available()).Times(available_call_count); ON_CALL(serial_stream_, available()) .WillByDefault( testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); int input_index = 0; - EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + EXPECT_CALL(serial_stream_, read()).Times(input_message.size()); ON_CALL(serial_stream_, read()) .WillByDefault(testing::Invoke( - [input_message, &input_index]() -> int { return input_message[input_index++]; })); + [input_message, &input_index]() -> int { return input_message.at(input_index++); })); shell.process_input(); ASSERT_EQ(called_callback_, 3); ASSERT_EQ(argc_, 1); - EXPECT_STREQ(argv_[0], expected_argv[0]); + EXPECT_THAT(argv_, ::testing::ElementsAreArray(expected_argv)); } TEST_F(ShellTest, ProcessInputMessageTwoArgs) { - const char* input_message = "a 12\r"; - const char* expected_argv[] = {"a", "12"}; + const std::string input_message{"a 12\r"}; + const std::vector expected_argv{"a", "12"}; - int available_call_count = strlen(input_message) + 1; + int available_call_count = input_message.size() + 1; EXPECT_CALL(serial_stream_, available()).Times(available_call_count); ON_CALL(serial_stream_, available()) .WillByDefault( testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); int input_index = 0; - EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + EXPECT_CALL(serial_stream_, read()).Times(input_message.size()); ON_CALL(serial_stream_, read()) .WillByDefault(testing::Invoke( - [input_message, &input_index]() -> int { return input_message[input_index++]; })); + [input_message, &input_index]() -> int { return input_message.at(input_index++); })); shell.process_input(); ASSERT_EQ(called_callback_, 1); ASSERT_EQ(argc_, 2); - EXPECT_STREQ(argv_[0], expected_argv[0]); - EXPECT_STREQ(argv_[1], expected_argv[1]); + EXPECT_THAT(argv_, ::testing::ElementsAreArray(expected_argv)); } TEST_F(ShellTest, ProcessInputMessageThreeArgs) { - const char* input_message = "ab 12 3\r"; - const char* expected_argv[] = {"ab", "12", "3"}; + const std::string input_message{"ab 12 3\r"}; + const std::vector expected_argv{"ab", "12", "3"}; - int available_call_count = strlen(input_message) + 1; + int available_call_count = input_message.size() + 1; EXPECT_CALL(serial_stream_, available()).Times(available_call_count); ON_CALL(serial_stream_, available()) .WillByDefault( testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); int input_index = 0; - EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + EXPECT_CALL(serial_stream_, read()).Times(input_message.size()); ON_CALL(serial_stream_, read()) .WillByDefault(testing::Invoke( - [input_message, &input_index]() -> int { return input_message[input_index++]; })); + [input_message, &input_index]() -> int { return input_message.at(input_index++); })); shell.process_input(); ASSERT_EQ(called_callback_, 2); ASSERT_EQ(argc_, 3); - EXPECT_STREQ(argv_[0], expected_argv[0]); - EXPECT_STREQ(argv_[1], expected_argv[1]); - EXPECT_STREQ(argv_[2], expected_argv[2]); + EXPECT_THAT(argv_, ::testing::ElementsAreArray(expected_argv)); } TEST_F(ShellTest, ProcessInputMessageFourArgs) { - const char* input_message = "cde 12 3 456\r"; - const char* expected_argv[] = {"cde", "12", "3", "456"}; + const std::string input_message{"cde 12 3 456\r"}; + const std::vector expected_argv{"cde", "12", "3", "456"}; - int available_call_count = strlen(input_message) + 1; + int available_call_count = input_message.size() + 1; EXPECT_CALL(serial_stream_, available()).Times(available_call_count); ON_CALL(serial_stream_, available()) .WillByDefault( testing::Invoke([&available_call_count]() -> int { return --available_call_count; })); int input_index = 0; - EXPECT_CALL(serial_stream_, read()).Times(strlen(input_message)); + EXPECT_CALL(serial_stream_, read()).Times(input_message.size()); ON_CALL(serial_stream_, read()) .WillByDefault(testing::Invoke( - [input_message, &input_index]() -> int { return input_message[input_index++]; })); + [input_message, &input_index]() -> int { return input_message.at(input_index++); })); shell.process_input(); ASSERT_EQ(called_callback_, 3); ASSERT_EQ(argc_, 4); - EXPECT_STREQ(argv_[0], expected_argv[0]); - EXPECT_STREQ(argv_[1], expected_argv[1]); - EXPECT_STREQ(argv_[2], expected_argv[2]); - EXPECT_STREQ(argv_[3], expected_argv[3]); + EXPECT_THAT(argv_, ::testing::ElementsAreArray(expected_argv)); } } // namespace diff --git a/andino_hardware/README.md b/andino_hardware/README.md index efe0e9cc..38b67ff5 100644 --- a/andino_hardware/README.md +++ b/andino_hardware/README.md @@ -9,7 +9,7 @@ This package aims to provide the necessary information to the correct assembly o | 1 | SBC | Raspberry Pi 4 B (4 Gb) | [PiShop](https://www.pishop.us/product/raspberry-pi-4-model-b-2gb/), [TiendaTec](https://www.tiendatec.es/raspberry-pi/gama-raspberry-pi/1100-raspberry-pi-4-modelo-b-4gb-5056561800349.html) | If you want better performance you could buy the 8GB model | | 2 | Chassis | 2 x Print 3d Chassis + Rubber Tyre Wheels | [Chassis](./printing_model/chassis/), [Wheels Sparkfun](https://www.sparkfun.com/products/13259) | - | | 3 | Motors | 2 x Motor with Encoder | [Sparkfun](https://www.sparkfun.com/products/16413) | - | -| 4 | Microcontroller | Arduino Nano | [Amazon](https://www.amazon.es/RUIZHI-Interfaz-Controlador-Mejorada-Compatible/dp/B0CNGKG4MZ/ref=sr_1_6?dib=eyJ2IjoiMSJ9.gnHfW9VtlEjMns12dAyHXLyFAlaikWpFyoOQJpO0iJBR-zelggQTQ9n001SH_P6NQ9DO3gPetP2krm7GAGvJus6vz4Utqu8Hy1gol0Rq7nmtJITd70ZNi3linf9v1g1iP7MlBx98cBGLVvFy-O2kZnJ63uZDwOZzwz_kExJzUWAxroO3AjufqqGOQHswLfDfjH6jpOJt54xxpCaqurDccId2O0uGKOj6WpPz6iLSubpsPB479SWYPSncxWQzz2kO4VjT6HVzPS2uWi19TS-A9WXVZceLBiz9t25Pf39jiGQ.1sLxrQ94HdIoXBq4VcDFMZhzKoL3wyJoY-U6BmDI6fY&dib_tag=se&keywords=arduino+nano+v3&qid=1714468231&sr=8-6) | You can also use an Arduino Uno, but mind size | +| 4 | Microcontroller | Arduino Nano | [Amazon](https://www.amazon.es/RUIZHI-Interfaz-Controlador-Mejorada-Compatible/dp/B0CNGKG4MZ/ref=sr_1_6?dib=eyJ2IjoiMSJ9.gnHfW9VtlEjMns12dAyHXLyFAlaikWpFyoOQJpO0iJBR-zelggQTQ9n001SH_P6NQ9DO3gPetP2krm7GAGvJus6vz4Utqu8Hy1gol0Rq7nmtJITd70ZNi3linf9v1g1iP7MlBx98cBGLVvFy-O2kZnJ63uZDwOZzwz_kExJzUWAxroO3AjufqqGOQHswLfDfjH6jpOJt54xxpCaqurDccId2O0uGKOj6WpPz6iLSubpsPB479SWYPSncxWQzz2kO4VjT6HVzPS2uWi19TS-A9WXVZceLBiz9t25Pf39jiGQ.1sLxrQ94HdIoXBq4VcDFMZhzKoL3wyJoY-U6BmDI6fY&dib_tag=se&keywords=arduino+nano+v3&qid=1714468231&sr=8-6) | You can also use an Arduino Uno, but mind size. It should include a microUSB - USB cable. If not, you will need to purchase it. | | 5 | Motor Driver | L298N Dual H Bridge | [Amazon](https://www.amazon.com/Bridge-Stepper-Driver-Module-Controller/dp/B09T6K9RFZ/ref=sr_1_4?crid=37YY7JO6C3WVE&keywords=l298&qid=1685740618&sprefix=l29%2Caps%2C277&sr=8-4) | - | | 6 | Laser Scanner | RPLidar A1M8 | [RobotShop](https://www.robotshop.com/products/rplidar-a1m8-360-degree-laser-scanner-development-kit?_pos=3&_sid=b0aefcea1&_ss=r), [Amazon](https://www.amazon.es/dp/B07VLFGT27?ref_=cm_sw_r_cso_wa_apan_dp_RJ3AZC2XCEVDK0X2DCGA&starsLeft=1&th=1) | If no microUSB-USB cable is included, you will need to purchase one | | 7 | Camera | Raspi Camera Module V2, 8 MP | [Robotshop](https://www.robotshop.com/products/raspberry-pi-camera-module-v2), [Amazon](https://www.amazon.com/Raspberry-Pi-Camera-Module-Megapixel/dp/B01ER2SKFS?th=1) | - | @@ -75,12 +75,19 @@ When you gather all the parts, you should have the following (NOTE: the printed ### Motor-Arduino - + Some frequent errors: - If one of the motors rotates in the opposite direction (think about the orientation of the motors in the chassis) probably the output(+ and -) of the L298N's output should be toggled. - When moving forward the encoder values should increase while moving backwards they should decrease. If it is happening the other way around probably the A and B encoder signals should be toggled. +### Raspberry-Power + + + +*NOTE: depending on the power bank maximum output current, the motors may need to be powered with a voltage value lower than 9V. While a higher voltage value (up to 9V) leads to smoother operation (better motor speed control), it also increases their open-loop speed, which is noticeable particularly during motion start and varies according to the power bank quality (output current capabilities). Therefore, adjusting the output voltage to lower values (around 7V) may be required so as to make the motors work as expected. + +**NOTE: Ensure the ribbon cable is properly connected with the blue or silver side facing the USB ports. ## Microcontroller Configuration diff --git a/andino_hardware/docs/andino_diagram_arduino.jpg b/andino_hardware/docs/andino_diagram_arduino.jpg new file mode 100644 index 00000000..d271201c Binary files /dev/null and b/andino_hardware/docs/andino_diagram_arduino.jpg differ diff --git a/andino_hardware/docs/andino_diagram_raspberry.jpg b/andino_hardware/docs/andino_diagram_raspberry.jpg new file mode 100644 index 00000000..9b22cc2d Binary files /dev/null and b/andino_hardware/docs/andino_diagram_raspberry.jpg differ diff --git a/andino_hardware/docs/arduino_motor_diagram.png b/andino_hardware/docs/arduino_motor_diagram.png deleted file mode 100644 index 5897a801..00000000 Binary files a/andino_hardware/docs/arduino_motor_diagram.png and /dev/null differ