{"id":14977871,"url":"https://github.com/thenoobinventor/lidarbot","last_synced_at":"2025-04-07T05:08:26.332Z","repository":{"id":156269983,"uuid":"614504475","full_name":"TheNoobInventor/lidarbot","owner":"TheNoobInventor","description":"A differential drive robot is controlled using ROS2 Humble running on a Raspberry Pi 4 (running Ubuntu server 22.04). The vehicle is equipped with a raspberry pi camera for visual feedback and an RPlidar A1 sensor used for Simultaneous Localization and Mapping (SLAM), autonomous navigation and obstacle avoidance.","archived":false,"fork":false,"pushed_at":"2025-03-22T12:52:30.000Z","size":80416,"stargazers_count":137,"open_issues_count":0,"forks_count":29,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-04-07T05:08:01.900Z","etag":null,"topics":["differential-drive-robot","diy-robot","gazebosim","hardware-interface","imu","imu-sensor-broadcaster","lidar","motor-driver","mpu6050","navigation","pca9685","raspberry-pi","raspberry-pi-camera","ros2","ros2-control","ros2-humble","rplidar","rviz2","sensor-fusion","slam"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TheNoobInventor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-03-15T18:07:19.000Z","updated_at":"2025-03-30T06:30:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"e1f28553-43ab-4ebd-b000-b48dc322dbc7","html_url":"https://github.com/TheNoobInventor/lidarbot","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheNoobInventor%2Flidarbot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheNoobInventor%2Flidarbot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheNoobInventor%2Flidarbot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TheNoobInventor%2Flidarbot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TheNoobInventor","download_url":"https://codeload.github.com/TheNoobInventor/lidarbot/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247595334,"owners_count":20963943,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["differential-drive-robot","diy-robot","gazebosim","hardware-interface","imu","imu-sensor-broadcaster","lidar","motor-driver","mpu6050","navigation","pca9685","raspberry-pi","raspberry-pi-camera","ros2","ros2-control","ros2-humble","rplidar","rviz2","sensor-fusion","slam"],"created_at":"2024-09-24T13:56:28.319Z","updated_at":"2025-04-07T05:08:26.317Z","avatar_url":"https://github.com/TheNoobInventor.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lidarbot\n\n![ROS2 CI](https://github.com/TheNoobInventor/lidarbot/actions/workflows/.github/workflows/lidarbot_ci_action.yml/badge.svg)\n\nA differential drive robot is controlled using ROS2 Humble running on a Raspberry Pi 4 (running Ubuntu server 22.04). The vehicle is equipped with a Raspberry Pi camera for visual feedback and an RPlidar A1 sensor used for Simultaneous Localization and Mapping (SLAM), autonomous navigation and obstacle avoidance. Additionally, an MPU6050 inertial measurement unit (IMU) is employed by the `robot_localization` package on the robot, to fuse IMU sensor data and the wheel encoders data, using an Extended Kalman Filter (EKF) node, to provide more accurate robot odometry estimates.\n\nHardware components are written for the Waveshare Motor Driver HAT and MPU6050 sensor to be accessed by the `ros2_control` differential drive controller and Imu sensor broadcaster respectively, via the `ros2_control` resource manager.\n\n\u003cp align='center'\u003e\n    \u003cimg src=docs/images/real_mapping.gif width=\"600\"\u003e\n\u003c/p\u003e\n\nA preprint of this work is available [here](http://dx.doi.org/10.13140/RG.2.2.15748.54408).\n\n🚧\t***(Work in Progress)*** \n\n- [Lidarbot](#lidarbot)\n  - [🗃️ Package Overview](#️-package-overview)\n  - [🧰\tHardware](#hardware)\n    - [Part list](#part-list)\n    - [Project Wiring and Assembly](#project-wiring-and-assembly)\n  - [🔌\t Installation](#-installation)\n    - [Development Machine setup](#development-machine-setup)\n      - [WiringPi](#wiringpi)\n      - [MPU6050 library](#mpu6050-library)\n      - [Sourcing ROS Installation](#sourcing-ros-installation)\n      - [Gazebo Classic](#gazebo-classic)\n      - [Gazebo Fortress](#gazebo-fortress)\n      - [Display lidarbot model in RViz](#display-lidarbot-model-in-rviz)\n      - [Teleoperation](#teleoperation)\n      - [Twist mux](#twist-mux)\n      - [Robot localization](#robot-localization)\n    - [Lidarbot setup](#lidarbot-setup)\n      - [Motor Driver HAT](#motor-driver-hat)\n      - [Raspberry Pi Camera](#raspberry-pi-camera)\n      - [MPU6050 offsets](#mpu6050-offsets)\n      - [MPU6050 covariances](#mpu6050-covariances)\n  - [Network Configuration](#network-configuration)\n  - [ros2 control Framework](#ros2-control-framework)\n    - [Differential Drive Controller](#differential-drive-controller)\n    - [Joint State Broadcaster](#joint-state-broadcaster)\n    - [IMU Sensor Broadcaster](#imu-sensor-broadcaster)\n  - [Test Drive](#test-drive)\n    - [Gazebo](#gazebo)\n    - [Lidarbot](#lidarbot-1)\n    - [Motor Connection Checks](#motor-connection-checks)\n  - [Mapping](#mapping)\n    - [Gazebo](#gazebo-1)\n    - [Lidarbot](#lidarbot-2)\n  - [Aruco package](#aruco-package)\n    - [Generate ArUco marker](#generate-aruco-marker)\n    - [Webcam calibration](#webcam-calibration)\n    - [Aruco trajectory visualizer node](#aruco-trajectory-visualizer-node)\n  - [Navigation](#navigation)\n    - [Gazebo](#gazebo-2)\n    - [Lidarbot](#lidarbot-3)\n  - [Acknowledgment](#acknowledgment)\n\n\n## 🗃️ Package Overview\n- [`lidarbot_aruco`](./lidarbot_aruco/) : Contains configuration, launch and node files to use ArUco markers with lidarbot.\n- [`lidarbot_base`](./lidarbot_base/) : Contains the ROS2 control hardware component for the lidarbot with low-level code for the Waveshare Motor Driver HAT.\n- [`lidarbot_bringup`](./lidarbot_bringup/) : Contains hardware component for the MPU6050 module, launch files to bring up the camera, lidar and the real lidarbot.\n- [`lidarbot_description`](./lidarbot_description/) : Contains the URDF description files for lidarbot, sensors and `ros2 control`.\n- [`lidarbot_gazebo`](./lidarbot_gazebo/) : Contains configuration, launch and world files needed to simulate lidarbot in Gazebo Classic.\n- [`lidarbot_gz`](./lidarbot_gz/) : Contains urdf, launch and world files needed to simulate lidarbot in Gazebo Fortress.\n- [`lidarbot_navigation`](./lidarbot_navigation/) : Contains launch, configuration and map files used for lidarbot navigation.\n- [`lidarbot_slam`](./lidarbot_slam/) : Contains configuration files for the slam toolbox and RViz, launch file to generate maps using SLAM.\n- [`lidarbot_teleop`](./lidarbot_teleop/) : Contains configuration and launch files used to enable joystick control of the lidarbot in simulation and physically.\n\n## 🧰\tHardware\n### Part list\nThe following components were used in this project:\n\n| | Part |\n| --| --|\n|1| Raspberry Pi 4 (4 GB)|\n|2| SanDisk 32 GB SD Card (minimum)|\n|3| [Two wheel drive robot chassis kit](https://www.amazon.com/perseids-Chassis-Encoder-Wheels-Battery/dp/B07DNYQ3PX/ref=sr_1_9?crid=3T8FVRRMPFCIX\u0026keywords=two+wheeled+drive+robot+chassis\u0026qid=1674141374\u0026sprefix=two+wheeled+drive+robot+chas%2Caps%2C397\u0026sr=8-9)|\n|4| [Waveshare Motor Driver HAT](https://www.waveshare.com/wiki/Motor_Driver_HAT)|\n|5| 2 x [Motors with encoders and wire harness](https://s.click.aliexpress.com/e/_DBL19Mr|\n|6| MPU6050 board|\n|7| [RPlidar A1](https://s.click.aliexpress.com/e/_DdPdRS7)|\n|8| Raspberry Pi camera v1.3|\n|9| [3D printed stands for RPlidar A1 and RPi 4](https://www.thingiverse.com/thing:3970110)|\n|10| Mount for Raspberry Pi camera|\n|11| Powerbank for RPi 4 (minimum output: 5V 3A)|\n|12| Gamepad|\n|13| [Mini Travel Router (optional)](https://s.click.aliexpress.com/e/_DcgfT61)|\n|14| 3 Slot 18650 battery holder|\n|15| 3 x 18650 batteries to power Motor Driver HAT|\n|16| Female to Female Dupont jumper cables|\n|17| Spare wires|\n|18| Logitech C270 webcam (optional)|\n\nSome other tools or parts used in the project are as follows:\n\n| | Tool/Part |\n| --| --|\n|1| Soldering iron|\n|2| 3D printer|\n|3| Screwdriver set|\n|4| Double-sided tape|\n\nAdditionally, some nylon stand-offs were used in between the Raspberry Pi 4 and its 3D printed stand to make it easier to plug in the USB power cable from the powerbank.\n\n### Project Wiring and Assembly\n\nThe electronic components of the lidarbot are connected as shown below.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg title='Wiring diagram' src=docs/images/lidarbot_wiring.png width=\"800\"\u003e\n\u003c/p\u003e\n\nThe MPU6050 board pins were connected to the Raspberry Pi 4 GPIO pins as follows for use with the I2C communication protocol:\n\n| MPU6050 board | GPIO.BOARD| GPIO.BCM|\n| ----------- | ------------| ------ |\n| VCC         | 3.3V | 3.3V |\n| GND         | GND | GND |\n| SCL         | 05 | GPIO03 |\n| SDA         | 03 | GPIO02 |\n\nIn a previous hardware version of lidarbot, photo interrupters were used with encoder disks with 20 slots (included in the robot chassis kit). However, this setup proved restrictive in yielding satisfactory results in navigation due to the low number of encoder ticks, 20. Therefore, the robot chassis kit motors were replaced with the ones below with built-in encoders --- which have encoder ticks of approximately 1084, calculated using this [guide](https://automaticaddison.com/calculate-pulses-per-revolution-for-a-dc-motor-with-encoder/) from Automatic Addison.\n\nThese are the new motors used for lidarbot:\n\n\u003cp align=\"center\"\u003e\n  \u003cimg title='Motors' src=docs/images/motors.jpg width=\"400\"\u003e\n  \u003cimg title='Motor pins' src=docs/images/motor_pins.jpg width=\"400\"\u003e\n\u003c/p\u003e\n\nThe pins on right and left motors are connected to the GPIO pins as follows:\n\n| Right motor pins | GPIO.BOARD | GPIO.BCM|\n| ----------- | ------------| ------ |\n| C1 (or Encoder A) | 22 | GPIO25 |\n| C2 (or Encoder B) | 16 | GPIO23 |\n| VCC | 5V | 5V |\n| GND | GND | GND |\n\n| Left motor pins| GPIO.BOARD | GPIO.BCM|\n| ----------- | ------------| ------ |\n| C1 (or Encoder A) | 18 | GPIO24 |\n| C2 (or Encoder B) | 15 | GPIO22 |\n| VCC | 5V | 5V |\n| GND | GND | GND |\n\nWhere, \n\nC1 - counts the respective motor pulses and\n\nC2 - checks the direction of motion of the respective motor, that is, forward or reverse.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg title='MPU6050' src=docs/images/mpu6050.jpg width=\"400\"\u003e\n  \u003cimg title='Encoders' src=docs/images/encoders.jpg width=\"400\"\u003e\n\u003c/p\u003e\n\nThe screw terminal blocks on the Motor Driver HAT ([shown below](https://www.waveshare.com/wiki/Motor_Driver_HAT)) are connected to the motor pins, M+ and M-, and battery holder cables as follows: \n\n| Motor Driver HAT pin | Connected to| \n| -- | -- |\n| MA1 | Red wire (Left motor)| \n| MA2 | Black wire (Left motor)| \n| GND | Black wire (battery holder) | \n| VIN | Red wire (battery holder) | \n| MB1 | Red wire(Right motor)| \n| MB2 | Black wire (Right motor)| \n\n\u003cp align=\"center\"\u003e\n  \u003cimg title='Motor Driver HAT' src=docs/images/Motor_Driver_HAT.png width=\"400\"\u003e\n\u003c/p\u003e\n\nSolder the cables (provided) to the motors. Might need to use spare wires if the provided ones are too short to reach the Motor Driver Hat. Should the wheel(s) move in the direction opposite of what is expected, exchange the respective motor cables screwed into the terminal blocks.\n\nFinally, the Raspberry Pi camera is connected to the ribbon slot on the Raspberry Pi 4 and the RPlidar A1 sensor is plugged into one of the RPi 4's USB ports.\n\n\u003cp align='center'\u003e\n  \u003cimg title='Top View' src=docs/images/top_view.jpg width=\"600\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg title='Side View' src=docs/images/side_view.jpg width=\"600\"\u003e\n\u003c/p\u003e\n\n## 🔌\t Installation\n\n### Development Machine setup\n\nA development machine or PC (laptop or desktop) is used to run more computationally intensive applications like Gazebo and Rviz. Additionally, the PC can be used to remotely control lidarbot.  \n\nUbuntu 22.04 LTS is required for this project to work with ROS2 Humble. Ubuntu 22.04 LTS can be installed on a PC by following [this guide](https://ubuntu.com/tutorials/install-ubuntu-desktop#1-overview). The ROS2 Humble installation procedure is available [here](https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debians.html). The Desktop version is installed on the PC (which includes RViz):\n\n```\nsudo apt install ros-humble-desktop\n```\n\nThen install the ROS development tools:\n\n```\nsudo apt install ros-dev-tools\n```\n\nAfter the ROS2 Humble installation, create a workspace on the PC/development machine and clone this repository:\n\n```\nmkdir -p ~/dev_ws/src\ncd ~/dev_ws/src\ngit clone https://github.com/TheNoobInventor/lidarbot.git .\n```\n\nNext install all the [ROS dependencies](https://docs.ros.org/en/humble/Tutorials/Intermediate/Rosdep.html) for the lidarbot packages:\n\n```\ncd ~/dev_ws\nsudo rosdep init\nrosdep update\nrosdep install --from-paths src --ignore-src --rosdistro humble -r -y\n```\n\nAny ROS packages referred to subsequently are assumed to be installed using the `rosdep install` command above unless it is explicitly specified. \n\nTwo more dependencies need to be met before building the workspace: installing the WiringPi i2c library to use the Raspberry Pi 4 GPIO pins and dependencies for the MPU6050 RPi 4 C++ library.\n\n#### WiringPi\n\nTo be able to utilize the GPIO pins of the Raspberry Pi 4 and program them using C/C++, an unofficial WiringPi was installed. This is required as hardware interfaces used by `ros2_control` are currently written only in C++ and low-level communication between Waveshare's Motor Driver HAT and `ros2_control` is needed. \n\nThe library is installed by executing the following commands in a terminal:\n\n```\ncd ~/Downloads\ngit clone https://github.com/wbeebe/WiringPi.git\ncd WiringPi/\n./build\n```\nTo check the current gpio version run this:\n```\ngpio -v\n```\n\nThe reference article for the WiringPi library can be found [here](https://arcanesciencelab.wordpress.com/2020/10/29/getting-wiringpi-to-work-under-ubuntu-20-10-on-a-raspberry-pi-4b-w-4gb/).\n\n\n#### MPU6050 library\n\nAlex Mous' [C/C++ MPU6050 library](https://github.com/alex-mous/MPU6050-C-CPP-Library-for-Raspberry-Pi\n) for Raspberry Pi 4, with modifications to incorporate quaternions, was used to setup the `ros2_control` Imu sensor broadcaster in the [`lidarbot_bringup`](./lidarbot_bringup/) package.\n\nRecall that the MPU6050 module uses the I2C communication protocol, the i2c dependencies for using this library are installed with:\n\n```\nsudo apt install libi2c-dev i2c-tools libi2c0\n```\n\n#### Sourcing ROS Installation\n\nTo avoid manually sourcing the ROS installation (or underlay) in each terminal window opened, and if ROS2 Humble is the only distribution on the PC, the command to source the underlay is added to the respective shell configuration file. \n\nUsing bash:\n```\necho \"source /opt/ros/humble/setup.bash\" \u003e\u003e $HOME/.bashrc\n```\n\nUsing zsh:\n```\necho \"source /opt/ros/humble/setup.zsh\" \u003e\u003e $HOME/.zshrc\n```\n\nAdditionally, to avoid manually sourcing our workspace (or overlay), add the command to source the workspace to the respective configuration file. \n\nUsing bash:\n```\necho \"source ~/dev_ws/install/setup.bash\" \u003e\u003e $HOME/.bashrc\nsource $HOME/.bashrc\n```\n\nUsing zsh:\n```\necho \"source ~/dev_ws/install/setup.zsh\" \u003e\u003e $HOME/.zshrc\nsource $HOME/.zshrc\n```\n\nThe command: `source $HOME/.zshrc` sources the configuration file for use in the current terminal. However, this step is not necessary for terminal windows opened hereafter.\n\n---\n\nFinally, navigate to the workspace directory and run the build command:\n\n```\ncd ~/dev_ws\ncolcon build --symlink-install\n```\n\nThe `--symlink-install` argument uses symlinks instead of copies which saves you from having to rebuild every time you [tweak certain files](https://articulatedrobotics.xyz/ready-for-ros-5-packages/).\n\n\n#### Gazebo Classic\n\nTODO: Add update on using Gazebo Fortress\n\nGazebo classic, version 11, is the robot simulator used in the project and can be installed [here](https://classic.gazebosim.org/tutorials?tut=install_ubuntu\u0026cat=install). \n\n#### Gazebo Fortress\nTODO:\n\n#### Display lidarbot model in RViz\n\nThe installed [xacro](https://index.ros.org/p/xacro/github-ros-xacro/#humble) tool dependency is used to process the lidarbot URDF files and combine them into a single complete URDF file.\n\nThe `description_launch.py` launch file displays the model in RViz:\n\n```\nros2 launch lidarbot_description description_launch.py\n```\n\n\u003cp align='center'\u003e\n  \u003cimg src=docs/images/lidarbot_rviz.png width=\"800\"\u003e\n\u003c/p\u003e\n\nThe [joint_state_publisher_gui](https://index.ros.org/p/joint_state_publisher_gui/github-ros-joint_state_publisher/#humble) package is used to bringup a window with sliders to move non-static links in RViz. Set the `use_gui` argument to `true` to turn the left and right wheels of lidarbot:\n\n```\nros2 launch lidarbot_description description_launch.py use_gui:=true\n```\n\n\u003cp align='center'\u003e\n  \u003cimg src=docs/images/joint_state_publisher_gui.png width=\"800\"\u003e\n\u003c/p\u003e\n\nThe different arguments for the launch file, and their default values, can be viewed by adding `--show-args` at the end of launch command:\n\n```\nros2 launch lidarbot_description description_launch.py --show-args\n```\n\n#### Teleoperation\n\nA [wireless gamepad](https://www.aliexpress.com/item/1005005354226710.html), like the one shown below, is used to control lidarbot both in simulation and physically. \n\n\u003cp align='center'\u003e\n  \u003cimg src=docs/images/wireless_gamepad.jpg width=\"400\"\u003e\n\u003c/p\u003e\n\nThe [joy_tester](https://index.ros.org/p/joy_tester/#humble) package is used to test and map the gamepad (joystick) keys to control lidarbot. To use it, plug in the USB dongle in the PC, then run:\n\n```\nros2 run joy joy_node\n``` \n\nAnd the following, in a new terminal:\n```\nros2 run joy_tester test_joy\n```\n\nThis opens a GUI window like the one shown below,\n\n\u003cp align='center'\u003e\n  \u003cimg src=docs/images/joy_tester.png width=\"600\"\u003e\n\u003c/p\u003e\n\nClick each button and move each stick of the gamepad to confirm that the actions are shown in GUI. The numbers correspond to the axis of the buttons and joystics (sticks) that will be used in mapping the movements of lidarbot. \n\nThe gamepad configuration for this project is in [`joystick.yaml`](./lidarbot_teleop/config/joystick.yaml), where:\n\n| Button/stick | Button/stick axis | Function | \n| :--: | :--: | -- |\n| L1 button | 4 | Hold this enable button to move robot at normal speed| \n| Left stick | 2 | Move stick forward or backward for linear motion of the robot | \n| Right stick | 1 | Move stick left or right for angular motion of the robot| \n\nSetting `require_enable_button` to `true` ensures that L1 has to be held before using the sticks to move the robot and stops the robot once L1 is no longer pressed. \n\nTo enable turbo mode for faster speed, the `enable_turbo_button` option in the config file can be set to an unused button axis.\n\nThe `joy_node` parameter, `deadzone`, specifies the amount a joystick has to be moved for it to be [considered to be away from the center](https://github.com/ros-drivers/joystick_drivers/blob/ros2/joy/README.md). This parameter is normalized between -1 and 1. A value of `0.25` indicates that the joytsick has to be moved 25% of the way to the edge of an axis's range before that axis will output a non-zero value. \n\nThe `deadzone` parameter should be tuned to suit the performance of user's game controller.\n\n\n#### Twist mux\n\nThe [`twist_mux`](https://index.ros.org/p/twist_mux/github-ros-teleop-twist_mux/#humble) package is used to multiplex several velocity command sources, used to move the robot with an unstamped [geometry_msgs::Twist](http://docs.ros.org/en/api/geometry_msgs/html/msg/Twist.html) message, into a single one. These sources are assigned priority values to allow a velocity source to be used or disabled. In this project, the command velocity sources are from the joystick and navigation.\n\nThe `twist_mux` configuration file is in [`twist_mux.yaml`](./lidarbot_teleop/config/twist_mux.yaml), and is used in the gazebo and lidarbot bringup launch files, [`gazebo_launch.py`](./lidarbot_gazebo/launch/gazebo_launch.py) and [`lidarbot_bringup_launch.py`](./lidarbot_bringup/launch/lidarbot_bringup_launch.py) respectively.\n\nIt can be observed from the configuration file, that the joystick commmand velocity source has a higher priority, with an assigned value of `100`, compared to the navigation velocity source that is assigned a value of `10`.\n\n```\ntwist_mux:\n  ros__parameters:\n    topics:\n      navigation:\n        topic   : cmd_vel\n        timeout : 0.5\n        priority: 10\n      joystick:\n        topic   : cmd_vel_joy\n        timeout : 0.5\n        priority: 100\n```\n\n#### Robot localization\nTODO:\n\nekf.yaml\n\n```\nros2 launch lidarbot_bringup lidarbot_bringup_launch.py use_robot_localization:=false\n```\n\n### Lidarbot setup\n\nTo install ROS2 Humble on the Raspberry Pi, Ubuntu Server 22.04 was first flashed on a 32GB micro SD card, this process is detailed in this [guide](https://ubuntu.com/tutorials/how-to-install-ubuntu-on-your-raspberry-pi#1-overview).\n\nAfter inserting the SD card and booting up the Pi, the environment for ROS2 Humble is setup by following this [guide](https://docs.ros.org/en/humble/Installation/Ubuntu-Install-Debians.html). Afterwards, ROS-Base (Bare Bones) and ROS development tools are installed:\n\n```\nsudo apt install ros-humble-ros-base ros-dev-tools\n```\n\nSimilarly, after the ROS2 Humble installation, create a workspace on the Raspberry Pi and clone this repository:\n\n```\nmkdir -p ~/robot_ws/src\ncd ~/robot_ws/src\ngit clone https://github.com/TheNoobInventor/lidarbot.git .\n```\n\nInstall ROS dependencies:\n\n```\ncd ~/robot_ws\nsudo rosdep init\nrosdep update\nrosdep install --from-paths src --ignore-src -r -y \\ \n--skip-keys \"rviz2 gazebo_ros2_control gazebo_ros_pkgs\" --rosdistro humble\n```\n\n`rviz2` and the gazebo related packages are skipped in the ROS dependency installation process as they are only run on the PC and not on the robot --- the rosdep keys for the ROS2 Humble distribution are available [here](https://github.com/ros/rosdistro/blob/master/humble/distribution.yaml).\n\n[WiringPi i2c library](#wiringpi) and [MPU6050 RPi 4 C++ library](#mpu6050-library) are also installed before building the workspace --- a `Downloads` directory will need to be created to clone the WiringPi files.\n\nLikewise to avoid manually sourcing the underlay and overlay, the same steps employed in the [development machine setup](#sourcing-ros-installation) are followed but replacing `dev_ws` with `robot_ws` where necessary.\n\nAfterwards, navigate to the workspace directory then run build command:\n\n```\ncd ~/robot_ws\ncolcon build --symlink-install\n```\n\n#### Motor Driver HAT\n\nWaveshare's Motor Driver HAT was used to control the motors of lidarbot. The relevant files found in the [`include`](./lidarbot_base/include/lidarbot_base/) and [`src`](./lidarbot_base/src/) directories of the [`lidarbot_base`](./lidarbot_base/) package. These files were modified, from those made [available by Waveshare](https://www.waveshare.com/wiki/Motor_Driver_HAT) with new ones added as well, to find a workaround to determining the motor direction of rotation --- this is a relatively straightforward task if the motors used here had hall effect encoders affixed to them. \n\nThe library dependencies for using the Motor Driver HAT were met after installing the MPU6050 library.\n\nTODO: \n\nbrief on how hall effect sensor work\n\nPulse counts\n\nLink with diff_drive_controller\n\nPull up resistor with relevant links\n\nCode structure\n\n#### Raspberry Pi Camera\n\nThe following packages are installed to use the Raspberry Pi Camera v1.3:\n```\nsudo apt install libraspberrypi-bin v4l-utils raspi-config\n```\n\nSupport for the RPi camera v1.3 will need to be enabled by navigating the menu options after running the following command:\n\n```\nsudo raspi-config\n```\n\u003cp align='center'\u003e\n  \u003cimg src=docs/images/raspi_config_1.png width=\"400\"\u003e\n  \u003cimg src=docs/images/raspi_config_2.png width=\"400\"\u003e\n\u003c/p\u003e\n\n\u003cp align='center'\u003e\n  \u003cimg src=docs/images/raspi_config_3.png width=\"400\"\u003e\n  \u003cimg src=docs/images/raspi_config_4.png width=\"400\"\u003e\n\u003c/p\u003e\n\nConfirm the RPi camera is connected by running this command:\n\n```\nvcgencmd get_camera\n```\n\nThis should output the following result:\n```\nsupported=1 detected=1, libcamera interfaces=0\n```\n\n#### MPU6050 offsets\n\nPrior to using the [Imu sensor broadcaster](https://index.ros.org/p/imu_sensor_broadcaster/github-ros-controls-ros2_controllers/#humble), the MPU6050 module needs to be calibrated to filter out its sensor noise/offsets. This is done in the following steps:\n\n- Place the lidarbot on a flat and level surface and unplug the RPlidar. \n- Generate the MPU6050 offsets. A Cpp executable is created in the CMakeLists.txt file of the `lidarbot_bringup` package before generating the MPU6050 offsets. This section of the [CMakeLists.txt](./lidarbot_bringup/CMakeLists.txt) file is shown below:\n\n  ```\n  # Create Cpp executable\n  add_executable(mpu6050_offsets src/mpu6050_lib.cpp src/mpu6050_offsets.cpp)\n\n  # Install Cpp executables\n  install(TARGETS\n    mpu6050_offsets\n    DESTINATION lib/${PROJECT_NAME}\n  )\n  ```\n\n  Build the `lidarbot_bringup` package:\n  ```\n  colcon build --symlink-install --packages-select lidarbot_bringup\n  ```\n\n  Run the executable:\n\n  ```\n  ros2 run lidarbot_bringup mpu6050_offsets\n  ```\n\n  Which outputs something like this:\n\n  ```\n  Please keep the MPU6050 module level and still. This could take a few minutes.\n\n  Calculating offsets ...\n\n  Gyroscope offsets:\n  ------------------\n  X: -104.689\n  Y: 651.005\n  Z: -158.596\n\n  Accelerometer offsets:\n  ----------------------\n  X: -13408.8\n  Y: 2742.39\n  Z: -14648.9\n\n  Include the obtained offsets in the respective macros of the mpu6050_lib.h file.\n  ```\n\n- Calibrate the MPU6050 module. Substitute the generated offsets into this section of the [`mpu6050_lib.h`](./lidarbot_bringup/include/lidarbot_bringup/mpu6050_lib.h) file:\n\n  ```\n  //Offsets - supply your own here (calculate offsets with getOffsets function)\n  //    Gyroscope\n  #define G_OFF_X -105\n  #define G_OFF_Y 651\n  #define G_OFF_Z -159\n  //     Accelerometer\n  #define A_OFF_X -13409 \n  #define A_OFF_Y 2742 \n  #define A_OFF_Z -14649\n  ```\n- Afterwards, the `lidarbot_bringup` package is rebuilt to reflect any changes made to the `mpu6050_lib.h` file.\n\nThe MPU6050 module is set to its most sensitive gyroscope and accelerometer ranges, which can be confirmed (or changed) at the top of the `mpu6050_lib.h` file.\n\n#### MPU6050 covariances\n\nTODO:\nQuick difference between variance and covariances\n \nIMU covariances required in `controllers.yaml`\n\nGenerate covariances for MPU6050 module:\n\n```ros2 run lidarbot_bringup mpu6050_covariances```\n\nExecutable output:\n\n```\nPlease keep the MPU6050 module level and still.\nReading and appending 500 sensor data points to respective arrays, it may take a while ...\n\nCalculating variances ...\n\nstatic_covariance_orientation: [5.46409e-06, 0.0, 0.0, 5.72254e-06, 0.0, 0.0, 4.22433e-07, 0.0, 0.0]\nstatic_covariance_angular_velocity: [2.01015e-06, 0.0, 0.0, 1.9657e-06, 0.0, 0.0, 8.13776e-07, 0.0, 0.0]\nstatic_covariance_linear_acceleration: [0.000632951, 0.0, 0.0, 0.000801987, 0.0, 0.0, 0.00117363, 0.0, 0.0]\n\nPaste covariance arrays in the imu_broadcaster ros__parameters section in lidarbot_bringup/config/controllers.yaml.\n\n```\n\n\n## Network Configuration\n\nTODO: update with Mini Router setup\n\nBoth the development machine and lidarbot need to be connected to the same local network as a precursor to bidirectional communication between the two systems. This [guide](https://roboticsbackend.com/ros2-multiple-machines-including-raspberry-pi/) by Robotics Backend was used in configuring the network communication. \n\nTo ensure communication between the dev machine and lidarbot, firstly, the firewall on the development machine had to be disabled (the firewall on the Ubuntu server was disabled by default):\n\n```\nsudo ufw disable\n```\n\nThe next step is to export `ROS_DOMAIN_ID` numbers, between 1 and 232, to the shell configurations of both systems:\n\n```\necho \"export ROS_DOMAIN_ID=31\" \u003e\u003e ~/.zshrc\n```\n\nThen source the shell configuration file:\n```\nsource $HOME/.zshrc\n```\n\nBoth systems might need to be rebooted to effect these changes.\n\nA static IP address was assigned to lidarbot on the router for easy discoverability on the network. Furthermore, it is advisable to use a router that supports at least the WiFi 5 wireless standard to avoid excessive data lag on RViz and terminal crashes when recording a [ros2bag](https://index.ros.org/p/ros2bag/github-ros2-rosbag2/#humble) for instance.\n\n***NOTE:*** \nBest practices might not have been employed in establishing communication between the two systems. The approach proposed [here](https://docs.ros.org/en/humble/How-To-Guides/Installation-Troubleshooting.html) was unable to be replicated in this project but others might find better success.\n\n## ros2 control Framework\n\n\u003cp align=\"center\"\u003e\n  \u003cimg title='ros2 control architecture' src=docs/images/ros2_control_arch.png width=\"800\"\u003e\n\u003c/p\u003e\n\nImage adapted from [Denis Štogl](https://vimeo.com/649651707) (CC-BY)\n\n### Differential Drive Controller\n\n### Joint State Broadcaster\n\n### IMU Sensor Broadcaster\n\n\n\n## Test Drive\n\n### Gazebo\n\n### Lidarbot\n\n### Motor Connection Checks\n\nA [ROS service](https://foxglove.dev/blog/creating-ros2-services) was written to test the connections of the motor(s) and by extension to know if the motor is faulty. Before running the tests, ensure that the 18650 batteries are charged, then prop the robot on a box or similar to prevent it falling off an edge for instance. \n\nRun the [client node](./lidarbot_base/src/motor_checks_client.cpp) to request the motor checks:\n\n```\nros2 run lidarbot_base motor_checks_client\n```\n\nThen run the [server node](./lidarbot_base/src/motor_checks_server.cpp) to check the motors:\n\n```\nros2 run lidarbot_base motor_checks_server\n```\nThese are the steps followed by the server node:\n- The server initializes the left and right motor pulse counts to 0. \n- It then runs each motor in the forward direction at 50% speed for 2 seconds. The terminal output is shown below:\n  ```\n  [INFO] [1699988646.067449176] [rclcpp]: Ready to check motors\n  [INFO] [1699988648.190017279] [rclcpp]: Received request to check motors...\n  [INFO] [1699988648.190117076] [rclcpp]: Checking left motor...\n  [INFO] [1699988652.196330587] [rclcpp]: Checking right motor...\n  [INFO] [1699988656.202619229] [rclcpp]: Sending back response...\n  ```\n- The current pulse counts for each motor are checked to confirm that the pulse is above 0.\n- If both motors have their pulse counts above 0, a success message is sent to the client: \n\n  ```\n  [INFO] [1699991078.643028687] [rclcpp]: service not available, waiting again...\n  [INFO] [1699991087.233641544] [rclcpp]: The checks were successful!\n  ```\n- If one or both of the motors do not have pulse counts above zero a warning message is sent from the server to the client and identifies the faulty motor(s). At the moment, however, the message sent to the client does not identify the faulty motor but instead outputs this message when there is an error:\n\n  ```\n  terminate called after throwing an instance of 'std::future_error'\n    what():  std::future_error: No associated state\n  [ros2run]: Aborted\n  ```\n  This section will be updated once the issue has been fixed.\n\n\u003cp align='center'\u003e\n    \u003cimg src=docs/images/motor_connection_tests.gif width=\"400\"\u003e\n\u003c/p\u003e\n\n\nIf a motor moves backward instead of forward, swap the cables for the specific motor to change the direction.\n\nAfter it is confirmed that both motors moved forward, lidarbot can be driven around with the gamepad (with the joystick and button configuration presented [here](#teleoperation)) by running this command:\n\n```\nros2 launch lidarbot_bringup lidarbot_bringup_launch.py\n```\n\n**Note:** There are some warning and error messages outputted to the terminal related to the camera. These are mostly related to calibrating the camera and can be ignored. \n\n## Mapping\n\nTODO: Brief overview of slam_toolbox\n\n### Gazebo\n\nBefore starting the mapping operation, ensure that the `mode` key, under `ROS Parameters` in the [`mapper_params_online_async.yaml`](./lidarbot_slam/config/mapper_params_online_async.yaml) file, is set to `mapping` and also that the `map_file_name`, `map_start_pose` and the `map_start_at_dock` keys are commented out:\n\n``` \n# ROS Parameters\nodom_frame: odom\nmap_frame: map\nbase_frame: base_footprint\nscan_topic: /scan\nuse_map_saver: true\nmode: mapping #localization\n\n# if you'd like to immediately start continuing a map at a given pose\n# or at the dock, but they are mutually exclusive, if pose is given\n# will use pose\n#map_file_name: /path/to/map_file\n#map_start_pose: [0.0, 0.0, 0.0]\n#map_start_at_dock: true \n```\n\nTo start mapping in a simulation environment, launch the Gazebo simulation of lidarbot on the development machine (which includes the joystick node for teleoperation):\n\n```\nros2 launch lidarbot_gazebo gazebo_launch.py\n```\n\nIn a separate terminal, navigate to the workspace directory, `lidarbot_ws` for example, and launch `slam_toolbox` with the [`online_async_launch.py`](./lidarbot_slam/launch/online_async_launch.py) file:\n\n```\nros2 launch lidarbot_slam online_async_launch.py \\\nslam_params_file:=src/lidarbot_slam/config/mapper_params_online_async.yaml \\\nuse_sim_time:=true\n```\n\n***Note**: The online asynchronous mode in `slam_toolbox` uses live and the most recent scan data to create a map, to avoid any lags therefore, some scans can be skipped.*\n\nIn another terminal, navigate to the workspace directory again and start `rviz2` with the `lidarbot_slam.rviz` config file:\n\n```\nrviz2 -d src/lidarbot_slam/rviz/lidarbot_slam.rviz\n```\n\nDrive around the obstacles to generate a map of the environment:\n\n\u003cp align='center'\u003e\n  \u003cimg src=docs/images/gazebo_mapping.gif width=\"800\"\u003e\n\u003c/p\u003e\n\nAfter generating the map, in the **SlamToolboxPlugin** in RViz, type in a name for the map in the field beside the **Save Map** button, then click on it. \n\n\u003cp align='center'\u003e\n    \u003cimg src=docs/images/save_map.png width=\"400\"\u003e\n\u003c/p\u003e\n\nThe saved map can be found in the workspace directory and will be used by [Nav2 stack](https://navigation.ros.org/) for navigation. \n\n### Lidarbot\n\nRun the following command on lidarbot to brings up the camera, lidar and joystick:\n\n```\nros2 launch lidarbot_bringup lidarbot_bringup_launch.py\n```\n\nFirst ensure that the [`mapper_params_online_async.yaml`](./lidarbot_slam/config/mapper_params_online_async.yaml) file is configured for mapping (refer to the previous subsection). Then open a new terminal, on the development machine, navigate to the workspace directory and launch `slam_toolbox` with the `use_sim_time` parameter set to `false`:\n\n```\nros2 launch lidarbot_slam online_async_launch.py \\\nslam_params_file:=src/lidarbot_slam/config/mapper_params_online_async.yaml \\\nuse_sim_time:=false\n```\nIn a new terminal, also on the development machine, navigate to the workspace directory again and start `rviz2`:\n\n```\nrviz2 -d src/lidarbot_slam/rviz/lidarbot_slam.rviz\n```\n\nDrive around the environment to generate a map:\n\n\u003cp align='center'\u003e\n    \u003cimg src=docs/images/real_mapping.gif width=\"600\"\u003e\n\u003c/p\u003e\n\nThen save the generated map.\n\n\n## Aruco package\n\n### Generate ArUco marker\n\nThe `opencv-contrib-python` module needs to be installed and not `opencv-python`: \n\n```\npip uninstall opencv-python\npip install opencv-contrib-python\n```\nThe computer might need to be restarted for the install to be effected.\n\nNext navigate to the path in the `lidarbot_aruco` directory:\n\n```\ncd ~/dev_ws/lidarbot_aruco/lidarbot_aruco\n```\n\nThen run the following script:\n\n```python\npython generate_aruco_marker.py --id 24 --type DICT_4X4_50 \\\n\t--output ../tags/DICT_4X4_50_id24.png\n```\n\nThe script arguments:\n\n`--id` : The unique identifier of the ArUco tag — this is a required argument and ID must be a valid ID in the ArUco dictionary used to generate the tag\n    \n`--type` : The name of the ArUco dictionary used to generate the tag; the default type is `DICT_4X4_50`\n\n`--output` : The path to the output image where the generated ArUco tag will be saved; this is a required argument\n\nRunning the previous script opens a window with the generated ArUco tag displayed,\n\n\u003cp align='center'\u003e\n    \u003cimg src=docs/images/generated_aruco_marker.png width=\"400\"\u003e\n\u003c/p\u003e\n\nTo close the window, press the **q** key on the keyboard on the opened window.\n\n### Webcam calibration\n\nThe Logitech webcam C270 HD is used in this project and needs to be calibrated.\n\nFirst install the [ROS usb camera driver](https://index.ros.org/r/usb_cam/#humble) package:\n\n```bash\nsudo apt install ros-humble-usb-cam\n```\n\nCamera calibration was done following the steps outlined this [guide](https://automaticaddison.com/how-to-perform-pose-estimation-using-an-aruco-marker/)\n\nExecute the command below to run the usb-cam driver node:\n\n```bash\nros2 run usb_cam usb_cam_node_exe --ros-args --params-file ~/dev_ws/src/lidarbot_aruco/config/params_1.yaml\n```\n\n### Aruco trajectory visualizer node\n\n```bash\nros2 run lidarbot_aruco aruco_trajectory_visualizer_node\n```\n\nLaunch file to bringup the usb driver and aruco trajectory visualizer node:\n\n```bash\nros2 launch lidarbot_aruco trajectory_visualizer_launch.py\n```\n\u003cp align='center'\u003e\n    \u003cimg src=docs/images/lidarbot_aruco_marker.png width=\"600\"\u003e\n\u003c/p\u003e\n\n\n\u003cp align='center'\u003e\n    \u003cimg src=docs/images/lidarbot_aruco_test.gif width=\"800\"\u003e\n\u003c/p\u003e\n\n\n## Navigation\n\n\u003cp align='center'\u003e\n    \u003cimg src=docs/images/nav2_topics.png width=\"800\"\u003e\n\u003c/p\u003e\n\nImage adapted from [Linorobot2](https://github.com/linorobot/linorobot2) \n\nTODO: Brief overview\n\n### Gazebo\n\nNav2's `amcl` package is used for localization with the map generated from ``slam_toolbox`. \n\nBring up lidarbot in Gazebo Fortress:\n\n```\nros2 launch lidarbot_gz gz_launch.py\n```\n\nBring up lidarbot in Gazebo classic:\n\n```\nros2 launch lidarbot_gazebo gazebo_launch.py\n```\n\nNavigate to the development workspace and open the following terminals.\n\nTo open `rviz`:\n\n```\nrviz2 -d src/lidarbot_navigation/rviz/lidarbot_nav.rviz\n```\n\nTo localize lidarbot with `amcl` in the map generated by `slam_toolbox`:\n```\nros2 launch lidarbot_navigation localization_launch.py \\\nmap:=./src/lidarbot_navigation/maps/sim_map.yaml use_sim_time:=true\n```\n\nTo launch navigation:\n```\nros2 launch lidarbot_navigation navigation_launch.py use_sim_time:=true \\\nmap_subscribe_transient_local:=true\n```\n\nIn rviz, set the initial pose using the `2D Pose Estimate` button in the toolbar so that lidarbot is aligned correctly both in rviz and Gazebo classic. Afterwards, click on the `2D Goal Pose` and choose a place on the map for lidarbot to navigate to:\n\n\u003cp align='center'\u003e\n    \u003cimg src=docs/images/gazebo_navigation.gif width=\"800\"\u003e\n\u003c/p\u003e\n\nTODO: add gif of navigation in Gazebo Fortress\n\nNote about the pixels not part of the map\n\nThe blue arrow shows unfiltered odometry, while green shows the filtered odometry. \n\n### Lidarbot\n\n```\nros2 launch lidarbot_bringup lidarbot_bringup_launch.py\n```\n\n```\nrviz2 -d src/lidarbot_navigation/rviz/lidarbot_nav.rviz\n```\n\n```\nros2 launch lidarbot_navigation localization_launch.py map:=./real_map.yaml use_sim_time:=false\n```\n\n```\nros2 launch lidarbot_navigation navigation_launch.py use_sim_time:=false \\\nmap_subscribe_transient_local:=true\n```\n\nUsing navigation goal button from nav2 plugin\n\n## Acknowledgment\n\n- [Articulated Robotics](https://articulatedrobotics.xyz/)\n- [Automatic Addison](https://automaticaddison.com/)\n- [Diffbot](https://github.com/ros-mobile-robots/diffbot)\n- [Linorobot2](https://github.com/linorobot/linorobot2)\n- [Mini pupper](https://github.com/mangdangroboticsclub/mini_pupper_ros)\n- [Pyimagesearch](https://pyimagesearch.com/)\n- [Robotics Backend](https://roboticsbackend.com/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthenoobinventor%2Flidarbot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthenoobinventor%2Flidarbot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthenoobinventor%2Flidarbot/lists"}