ソフトウェアセットアップ -Ubuntu MATE-
ライトローバーを動作させせるためには、OSや各ライブラリのセットアップが必要です。
以下の手順で、ライトローバーを動作させるための環境を構築していきましょう。
なお本ページはROS 2をUbuntu MATEで使用する場合の手順です。
ROS1については、Raspberry Pi OSを使用する場合はこちら、
Ubuntu MATEを使用する場合はこちらをご参照ください。
OSのセットアップと設定
OSのセットアップや、ライトローバーを動作させるために必要な設定などを行います。
なおライトローバーでROS2を動作せる場合、消費電力の関係上、電源はRaspberry PiのUSB Type-Cポートに接続することを強く推奨します。
バッテリーを使用する場合も、Rapberry Piの電源ポートを使用することで動作が安定します。
Ubuntu MATEのセットアップ
OSの書込み
microSDカードにRaspberry Piに対応したOS「Ubuntu MATE」を書込みます。
- 以下のリンクからライトローバー用のUbuntu MATE 24.04 imgファイルをダウンロードしてください。
ライトローバー用Ubuntu MATE 24.04 - イメージ書き込みソフトbalenaEtcherのインストーラーをダウンロードします。
- インストーラーを起動し、balenaEtcherをインストールします。
- microSDをカードリーダーを使ってPCに接続します。
- balenaEtcherを起動します。
- balenaEtcherのFlash from fileから、先ほどダウンロードしたUbuntu MATEのimgファイルを選択します。
- Select targetの項目は、OSを書き込みたいSDカードを選択します。
- Flash!でOSを書き込みます。
Raspberry Piの初期設定
Raspberry Piを起動し、各種設定を行います。
現在Vstoneにて配布しているimgファイルは、2の初期設定をすでに行っております。
ユーザー名とパスワードは以下の通りです。
ユーザー名 : pi
パスワード : raspberry
- Raspberry PiのUSB Cポートに電源を接続し起動します。
初期設定画面が表示されるので、以下の通りに設定します。- Welcome -> 日本語を選択
- キーボードレイアウト -> Japaneseを選択し、その中からJapanese (OADG 109A)を選択
- 無線 -> 「このネットワークに接続する」を選択し、接続したいネットワークのSSIDを選択
- Authentication required-> 選択したSSIDのパスワードを入力
- どこに住んでいますか? -> Tokyoと入力
- あなたの情報を入力してください -> ユーザー名、パスワード等を設定する
setup.pyの実行
Raspberry Piの設定変更と必須ライブラリの導入を行います。
手動で作業を行うこともできますが、以下のPythonファイルをご使用いただくことで自動で行えます。
次のリンクを右クリックし、「Save Link As ...」を選択して「ホームフォルダ」に保存してください。
〇setup.pyの実行方法
- ターミナルを起動して次のように入力し、ubuntu_setup.pyを実行します。
sudo apt update sudo apt upgrade sudo apt install python3-setuptools sudo python3 ubuntu24_setup.py
VNCの設定
ライトローバーをネットワーク経由で他のPCから制御できるようにVNCを導入します。
〇Raspberry Piの操作
-
ターミナルに以下のコマンドを入力し、vncをインストールします。
sudo apt install x11vnc -
下記のコマンドでvncのパスワードを設定します。
sudo x11vnc -storepasswd /etc/.vncpasswd -
テキストエディタで/etc/systemd/system/x11vnc.serviceを作製します。(例)nanoを使用する場合は以下のコマンドになります。
sudo nano /etc/systemd/system/x11vnc.service -
以下の内容を書き込み、保存します。
[Unit] Description=x11vnc (Remote access) After=network-online.target [Service] Type=simple ExecStart=/usr/bin/x11vnc -auth guess -display :0 -rfbauth /etc/.vncpasswd -rfbport 5900 -forever -loop -noxdamage -repeat -shared ExecStop=/bin/kill -TERM $MAINPID ExecReload=/bin/kill -HUP $MAINPID KillMode=control-group Restart=on-failure [Install] WantedBy=graphical.target -
以下の3つのコマンドを実行します。
sudo systemctl daemon-reload sudo systemctl enable x11vnc sudo systemctl start x11vnc -
テキストエディタで/boot/firmware/config.txtを編集します。
sudo nano /boot/firmware/config.txt -
指定の行を以下のように修正します。
55行目
#dtoverlay=dwc2,dr_mode=host最後に以下を追記し、保存します。
disable_overscan=1 hdmi_force_hotplug=1 hdmi_group=2 hdmi_mode=82 -
一度電源を切り再起動します。
sudo poweroff -
下記のコマンドでIPアドレスを確認します。
ip addr
〇PCの操作
- ライトローバーと同じネットワークに接続してください。
- ブラウザからREALVNCのダウンロードページにアクセスし、VNC Viewerのインストーラーをダウンロードしてください。
- インストーラーを起動し、VNC Viewerをインストールします。
- インストールしたVNC Viewerを起動します。
- ウィンドウの上部にある入力欄に、先ほど確認したライトローバーのIPアドレスを入力します。
- UsernameにUbuntuのユーザー名、PasswordにはVNCで設定したパスワードを入力します。
ROS2のセットアップ
ROS2 Humbleのインストール
ROS2 Humbleをインストールします。
基本的には以下のサイトの説明に沿って進めて行きます。
ROS2 Documentation: Humble
-
ロケールの設定
UTF-8対応のロケールを設定します。
ターミナルで以下のコマンドを実行してください。sudo apt update && sudo apt install -y locales sudo locale-gen en_US en_US.UTF-8 sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 export LANG=en_US.UTF-8 -
ROS2リポジトリの設定
ROS2のリポジトリをソースリストに追加することで、インストールできるようにします。
まず以下の2つのコマンドを実行してください。sudo apt install software-properties-common sudo add-apt-repository universe
ターミナルで次の2つのコマンドを実行してください。sudo apt update && sudo apt install -y curl gnupg lsb-release sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg最後にsource listにリポジトリを追加します。
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null -
ROS2 Jazzyのインストール 以下のコマンドを実行してROS2 Jazzyをインストールします。
sudo apt update sudo apt upgrade sudo apt install ros-jazzy-desktop sudo apt install ros-dev-tools -
環境変数の設定 ターミナルからコマンドを呼び出して実行する際には、そのコマンドに対するパスが通っている必要があります。
ターミナルを起動した際にROSへのパスが通るように設定を追加します。
ターミナルを立ち上げて以下のコマンドを実行してください。source /opt/ros/jazzy/setup.bash echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc
以上で、ROS2 Jazzyのインストールは完了です。
ワークスペースの作成
その他のパッケージのインストール用に新たにros2用のワークスペースを作成しましょう。
次のコマンドでワークスペースとなるフォルダを作成してください。
mkdir -p ~/ros2_ws/src
以上で、ワークスペースの作成が完了です。
lightrover_ros2パッケージのインストール
作成したワークスペースにライトローバーをROS2から制御するためのパッケージ「lightrover_ros2」をセットアップしましょう。
新たにターミナルを立ち上げて、次のコマンドでワークスペースのsrcフォルダに移動します。
cd ~/ros2_ws/src
次のコマンドで、Githubからlightrover_rosパッケージのソースをクローンします。
git clone --recursive -b jazzy https://github.com/vstoneofficial/lightrover_ros2.git
このままではパッケージに含まれるPythonファイルに実行権限がなく起動できません。
以下のコマンドで権限を付けましょう。
sudo chmod +x ~/ros2_ws/src/lightrover_ros2/lightrover_ros/lightrover_ros/*.py
これで実行する準備が整いました。
ワークスペースをビルドします。
cd ~/ros2_ws
colcon build --symlink-install --cmake-clean-cache --parallel-workers 2
これでlightrover_ros2パッケージのインストールは完了です。
現在のターミナルはパスが通っていないので、ターミナルを立ち上げ直してパスが通っている状態にしてください。
YDLiDARのセットアップ
ライトローバーに取付けられているLiDAR「YDLiDAR X2」を使用できるように、セットアップを行っていきましょう。
まずは、YDLiDARのSDKをインストールします。
以下のコマンドでホームフォルダに移動します。
cd ~
SDKのセットアップに必要なパッケージをインストールします。
sudo apt install cmake pkg-config
sudo apt install swig python3-pip
次のコマンドで、GithubからYDLiDAR SDKパッケージのソースをクローンします。
git clone https://github.com/YDLIDAR/YDLidar-SDK.git
以下のコマンドを順に実行し、YDLiDAR SDKをメイクしてインストールしましょう。
cd YDLidar-SDK/
mkdir build
cd build/
cmake ..
make
sudo make install
続いてYDLiDAR用のROSデバイスドライバパッケージを入手します。 ワークスペースのsrcフォルダに移動しましょう。
cd ~/ros2_ws/src
次のコマンドで、Githubからydlidar_rosパッケージのソースをクローンします。
git clone https://github.com/YDLIDAR/ydlidar_ros2_driver.git
/ydlidar_ros2_diver/src/ydlidar_ros2_driver_node.cppを以下のように修正します。
#ifdef _MSC_VER
#ifndef _USE_MATH_DEFINES
#define _USE_MATH_DEFINES
#endif
#endif
#include "src/CYdLidar.h"
#include <math.h>
#include <chrono>
#include <iostream>
#include <memory>
#include "rclcpp/clock.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp/time_source.hpp"
#include "sensor_msgs/msg/laser_scan.hpp"
#include "std_srvs/srv/empty.hpp"
#include <vector>
#include <iostream>
#include <string>
#include <signal.h>
#define ROS2Verision "1.0.1"
int main(int argc, char *argv[]) {
rclcpp::init(argc, argv);
auto node = rclcpp::Node::make_shared("ydlidar_ros2_driver_node");
RCLCPP_INFO(node->get_logger(), "[YDLIDAR INFO] Current ROS Driver Version: %s\n", ((std::string)ROS2Verision).c_str());
CYdLidar laser;
// --- port ---
std::string str_optvalue = "/dev/ydlidar";
node->declare_parameter<std::string>("port", str_optvalue);
node->get_parameter("port", str_optvalue);
laser.setlidaropt(LidarPropSerialPort, str_optvalue.c_str(), str_optvalue.size());
// --- ignore_array --- (元コードは string として扱っているので string で宣言)
str_optvalue = "";
node->declare_parameter<std::string>("ignore_array", str_optvalue);
node->get_parameter("ignore_array", str_optvalue);
laser.setlidaropt(LidarPropIgnoreArray, str_optvalue.c_str(), str_optvalue.size());
// --- frame_id ---
std::string frame_id = "laser_frame";
node->declare_parameter<std::string>("frame_id", frame_id);
node->get_parameter("frame_id", frame_id);
//////////////////////int property/////////////////
/// lidar baudrate
int optval = 230400;
node->declare_parameter<int>("baudrate", optval);
node->get_parameter("baudrate", optval);
laser.setlidaropt(LidarPropSerialBaudrate, &optval, sizeof(int));
optval = TYPE_TRIANGLE;
node->declare_parameter<int>("lidar_type", optval);
node->get_parameter("lidar_type", optval);
laser.setlidaropt(LidarPropLidarType, &optval, sizeof(int));
optval = YDLIDAR_TYPE_SERIAL;
node->declare_parameter<int>("device_type", optval);
node->get_parameter("device_type", optval);
laser.setlidaropt(LidarPropDeviceType, &optval, sizeof(int));
optval = 9;
node->declare_parameter<int>("sample_rate", optval);
node->get_parameter("sample_rate", optval);
laser.setlidaropt(LidarPropSampleRate, &optval, sizeof(int));
optval = 4;
node->declare_parameter<int>("abnormal_check_count", optval);
node->get_parameter("abnormal_check_count", optval);
laser.setlidaropt(LidarPropAbnormalCheckCount, &optval, sizeof(int));
optval = 0;
node->declare_parameter<int>("intensity_bit", optval);
node->get_parameter("intensity_bit", optval);
laser.setlidaropt(LidarPropIntenstiyBit, &optval, sizeof(int));
//////////////////////bool property/////////////////
/// fixed angle resolution
bool b_optvalue = false;
node->declare_parameter<bool>("fixed_resolution", b_optvalue);
node->get_parameter("fixed_resolution", b_optvalue);
laser.setlidaropt(LidarPropFixedResolution, &b_optvalue, sizeof(bool));
b_optvalue = true;
node->declare_parameter<bool>("reversion", b_optvalue);
node->get_parameter("reversion", b_optvalue);
laser.setlidaropt(LidarPropReversion, &b_optvalue, sizeof(bool));
b_optvalue = true;
node->declare_parameter<bool>("inverted", b_optvalue);
node->get_parameter("inverted", b_optvalue);
laser.setlidaropt(LidarPropInverted, &b_optvalue, sizeof(bool));
b_optvalue = true;
node->declare_parameter<bool>("auto_reconnect", b_optvalue);
node->get_parameter("auto_reconnect", b_optvalue);
laser.setlidaropt(LidarPropAutoReconnect, &b_optvalue, sizeof(bool));
b_optvalue = false;
node->declare_parameter<bool>("isSingleChannel", b_optvalue);
node->get_parameter("isSingleChannel", b_optvalue);
laser.setlidaropt(LidarPropSingleChannel, &b_optvalue, sizeof(bool));
b_optvalue = false;
node->declare_parameter<bool>("intensity", b_optvalue);
node->get_parameter("intensity", b_optvalue);
laser.setlidaropt(LidarPropIntenstiy, &b_optvalue, sizeof(bool));
b_optvalue = false;
node->declare_parameter<bool>("support_motor_dtr", b_optvalue);
node->get_parameter("support_motor_dtr", b_optvalue);
laser.setlidaropt(LidarPropSupportMotorDtrCtrl, &b_optvalue, sizeof(bool));
b_optvalue = false;
node->declare_parameter<bool>("debug", b_optvalue);
node->get_parameter("debug", b_optvalue);
laser.setEnableDebug(b_optvalue);
//////////////////////float property/////////////////
/// unit: °
float f_optvalue = 180.0f;
node->declare_parameter<float>("angle_max", f_optvalue);
node->get_parameter("angle_max", f_optvalue);
laser.setlidaropt(LidarPropMaxAngle, &f_optvalue, sizeof(float));
f_optvalue = -180.0f;
node->declare_parameter<float>("angle_min", f_optvalue);
node->get_parameter("angle_min", f_optvalue);
laser.setlidaropt(LidarPropMinAngle, &f_optvalue, sizeof(float));
f_optvalue = 64.f;
node->declare_parameter<float>("range_max", f_optvalue);
node->get_parameter("range_max", f_optvalue);
laser.setlidaropt(LidarPropMaxRange, &f_optvalue, sizeof(float));
f_optvalue = 0.1f;
node->declare_parameter<float>("range_min", f_optvalue);
node->get_parameter("range_min", f_optvalue);
laser.setlidaropt(LidarPropMinRange, &f_optvalue, sizeof(float));
f_optvalue = 10.f;
node->declare_parameter<float>("frequency", f_optvalue);
node->get_parameter("frequency", f_optvalue);
laser.setlidaropt(LidarPropScanFrequency, &f_optvalue, sizeof(float));
bool invalid_range_is_inf = false;
node->declare_parameter<bool>("invalid_range_is_inf", invalid_range_is_inf);
node->get_parameter("invalid_range_is_inf", invalid_range_is_inf);
//初始化
bool ret = laser.initialize();
if (ret)
{
//设置GS工作模式(非GS雷达请无视该代码)
int i_v = 0;
node->declare_parameter<int>("m1_mode", i_v);
node->get_parameter("m1_mode", i_v);
laser.setWorkMode(i_v, 0x01);
i_v = 0;
node->declare_parameter<int>("m2_mode", i_v);
node->get_parameter("m2_mode", i_v);
laser.setWorkMode(i_v, 0x02);
i_v = 1;
node->declare_parameter<int>("m3_mode", i_v);
node->get_parameter("m3_mode", i_v);
laser.setWorkMode(i_v, 0x04);
//启动扫描
ret = laser.turnOn();
}
else
{
RCLCPP_ERROR(node->get_logger(), "%s\n", laser.DescribeError());
}
auto laser_pub = node->create_publisher<sensor_msgs::msg::LaserScan>("scan", rclcpp::SensorDataQoS());
auto stop_scan_service =
[&laser](const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<std_srvs::srv::Empty::Request> req,
std::shared_ptr<std_srvs::srv::Empty::Response> response) -> bool
{
return laser.turnOff();
};
auto stop_service = node->create_service<std_srvs::srv::Empty>("stop_scan",stop_scan_service);
auto start_scan_service =
[&laser](const std::shared_ptr<rmw_request_id_t> request_header,
const std::shared_ptr<std_srvs::srv::Empty::Request> req,
std::shared_ptr<std_srvs::srv::Empty::Response> response) -> bool
{
return laser.turnOn();
};
auto start_service = node->create_service<std_srvs::srv::Empty>("start_scan",start_scan_service);
rclcpp::WallRate loop_rate(20);
//std::ofstream file("pointcloud_data.txt"); // 打开文件流
while (ret && rclcpp::ok())
{
LaserScan scan;
if (laser.doProcessSimple(scan))
{
auto scan_msg = std::make_shared<sensor_msgs::msg::LaserScan>();
scan_msg->header.stamp.sec = RCL_NS_TO_S(scan.stamp);
scan_msg->header.stamp.nanosec = scan.stamp - RCL_S_TO_NS(scan_msg->header.stamp.sec);
scan_msg->header.frame_id = frame_id;
scan_msg->angle_min = scan.config.min_angle;
scan_msg->angle_max = scan.config.max_angle;
scan_msg->angle_increment = scan.config.angle_increment;
scan_msg->scan_time = scan.config.scan_time;
scan_msg->time_increment = scan.config.time_increment;
scan_msg->range_min = scan.config.min_range;
scan_msg->range_max = scan.config.max_range;
int size = (scan.config.max_angle - scan.config.min_angle)/ scan.config.angle_increment + 1;
scan_msg->ranges.resize(size);
scan_msg->intensities.resize(size);
for (size_t i=0; i < scan.points.size(); i++)
{
const auto& p = scan.points.at(i);
int index = std::ceil((scan.points[i].angle - scan.config.min_angle)/scan.config.angle_increment);
if(index >=0 && index < size) {
scan_msg->ranges[index] = scan.points[i].range;
scan_msg->intensities[index] = scan.points[i].intensity;
}
//file << "i:" << i << ",a:" << p.angle << ",d:" << p.range << ",p:" << p.intensity << std::endl;
}
laser_pub->publish(*scan_msg);
}
else
{
RCLCPP_ERROR(node->get_logger(), "Failed to get scan");
}
if(!rclcpp::ok())
{
break;
}
rclcpp::spin_some(node);
loop_rate.sleep();
}
RCLCPP_INFO(node->get_logger(), "[YDLIDAR INFO] Now YDLIDAR is stopping .......");
laser.turnOff();
laser.disconnecting();
rclcpp::shutdown();
return 0;
}
/launch/ydlidar_launch.pyを以下のように修正します。
#!/usr/bin/python3
# Copyright 2020, EAIBOT
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch_ros.actions import LifecycleNode
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch.actions import LogInfo
import lifecycle_msgs.msg
import os
def generate_launch_description():
share_dir = get_package_share_directory('ydlidar_ros2_driver')
parameter_file = LaunchConfiguration('params_file')
node_name = 'ydlidar_ros2_driver_node'
params_declare = DeclareLaunchArgument('params_file',
default_value=os.path.join(
share_dir, 'params', 'X2.yaml'),
description='FPath to the ROS2 parameters file to use.')
driver_node = LifecycleNode(package='ydlidar_ros2_driver',
executable='ydlidar_ros2_driver_node',
name='ydlidar_ros2_driver_node',
output='screen',
emulate_tty=True,
parameters=[parameter_file],
node_namespace='/',
)
tf2_node = Node(package='tf2_ros',
executable='static_transform_publisher',
name='static_tf_pub_laser',
arguments=['0', '0', '0.02','0', '0', '0', '1','base_link','laser_frame'],
)
return LaunchDescription([
params_declare,
driver_node,
tf2_node,
])
続いて、YDLiDAR X2接続時の手間を省くため、シンボリックリンクを張る操作を行います。
Ubuntuにデバイスを接続するとデバイスファイルが作成されてアクセスできるようになるのですが、デバイスファイル名は接続ポートやタイミングによって変わってきてしまうため、毎回デバイスファイル名でアクセスするのは不便です。
次の手順でスクリプトを実行することで、ydlidarのデバイスファイルに対して決まった名前でシンボリックリンクを張るようになりますので、毎回決まった名前でアクセスすることが可能になります。
新しくターミナルを起動し、スクリプトがあるディレクトリに移動します。
cd ~/ros2_ws/src/ydlidar_ros2_driver/startup/
スクリプトファイルに権限をつけます。
sudo chmod 777 ./*
スクリプトファイルを実行しましょう。
sudo sh initenv.sh
スクリプトが完了したらターミナルを再起動し、Raspberry Piに接続されているYDLiDARのUSBケーブルを一度抜差ししてください。
ワークスペースをビルドします。
cd ~/ros2_ws
colcon build --symlink-install --cmake-clean-cache --parallel-workers 2
YDLiDARのセットアップは完了です。
同時に複数のライトローバーを動作させる場合
複数のライトローバーが同一ネットワークに接続された状態でROS2で動作させようとすると、
別の機体からの指令を受け取ってしまい動作がおかしくなります。
こういった事態を防ぐためにROS_DOMAIN_IDをあらかじめ指定しておきましょう。
ターミナルを立ち上げて以下のコマンドを実行してください。
なおROS_DOMAIN_IDは機体ごとに変更してください。
0~65535まで指定が可能です。
export ROS_DOMAIN_ID=1
これでROS_DOMAIN_IDの指定が出来ました。ライトローバーを走行させてみましょう。
複数の機体を動作させても信号が混ざらなくなります。
下記のように.bashrcに追記しておくことで、シェル起動時に自動的に設定されるようにすることもできます。
echo "export ROS_DOMAIN_ID=1" >> ~/.bashrc
以上で、ライトローバーをROS2で制御する準備が整いました。