tshell_blog

ソフトウェアと車輪がついた乗り物のはなし

ESP32CAMをMicro XRCE-DDSでROS2ノード化する

f:id:tshell:20210331213812p:plain

https://micro-xrce-dds.docs.eprosima.com/en/latest/

Micro XRCE-DDSはマイコンでも動作する軽量なクライアントライブラリで利用できるDDSとのインターフェースです。マイコンはMicro XRCE-DDS Agentと通信し,Micro XRCE-DDS AgentはマイコンとのやりとりをROS2が利用するDDSへ転送します。

この記事ではDocker for WindowsでMicro XRCE-DDS Agentを実行し,ESP32側ではros2arduinoをMicro XRCE-DDS Client として利用します。

環境

ホスト

  • Windows 10
  • Ubuntu 18.04 (docker)
  • ROS2 dashing (docker)
  • Micro XRCE-DDS Agent v1.3.0 (docker)

マイコン

  • ESP32-CAM
  • ros2arduino 0.2.1

ファイアウォールの設定

以下の説明ではMicro XRCE-DDS AgentはUDPの2018ポートを使用します。Windows ファイアウォールの設定を変更し,docker backendへのUDPでのアクセスを許可しておきます。

f:id:tshell:20210331214245p:plain

Micro XRCE-DDS Agentの準備

Micro XRCE-DDS Agentはdockerで動作させます。以下のDockerfileを作成します。

# docker build -t ros2:dashing .
FROM ubuntu:18.04
ENV DEBIAN_FRONTEND noninteractive

ENV ROS_DISTRO dashing

# ロケールのセットアップ
RUN apt-get update && apt-get install -y locales && \
    dpkg-reconfigure locales && \
    locale-gen ja_JP ja_JP.UTF-8 && \
    update-locale LC_ALL=ja_JP.UTF-8 LANG=ja_JP.UTF-8
ENV LC_ALL   ja_JP.UTF-8
ENV LANG     ja_JP.UTF-8
ENV LANGUAGE ja_JP.UTF-8

# APTソースリストの設定
RUN apt-get update && \
    apt-get install -y curl gnupg2 lsb-release && \
    curl http://repo.ros2.org/repos.key | apt-key add - && \
    sh -c 'echo "deb [arch=amd64,arm64] http://packages.ros.org/ros2/ubuntu \
    `lsb_release -cs` main" > /etc/apt/sources.list.d/ros2-latest.list' && \
    apt-get update

# ROS2パッケージのインストール
RUN export ROS_DISTRO=dashing && \
    apt-get install -y ros-$ROS_DISTRO-desktop \
    python3-colcon-common-extensions python3-rosdep python3-argcomplete && \
    rosdep init && \
    rosdep update

## 環境設定
RUN    echo "source /opt/ros/$ROS_DISTRO/setup.bash" >> ~/.bashrc

## Micro XRCE-DDS Agentのビルド
## https://github.com/ROBOTIS-GIT/ros2arduino
RUN apt-get install -y git
WORKDIR work
SHELL ["/bin/bash", "-c"]
RUN git clone https://github.com/eProsima/Micro-XRCE-DDS-Agent.git && \
    cd Micro-XRCE-DDS-Agent && \
    git checkout -b v1.3.0 refs/tags/v1.3.0 && \
    mkdir build && cd build && \
    source /opt/ros/dashing/setup.bash && \
    cmake .. && \
    make && \
    make install && \
    ldconfig /usr/local/lib/

Dockerfileを作成したら以下のコマンドでビルドします。

> docker build -t micro-xrce-dds-agent:v1.3.0 .

ビルドしたら以下コマンドで起動します。

> docker run --rm -it -p 192.168.0.10:2018:2018 -p 192.168.0.10:2018:2018/udp micro-xrce-dds-agent:v1.3.0
# cd MicroXRCEAgent/build
# ./MicroXRCEAgent udp4 -p 2018

起動すると以下のようなメッセージが表示されます。

root@docker-desktop:/work/Micro-XRCE-DDS-Agent/build# ./MicroXRCEAgent udp4 -p 2018
Press CTRL+C to exit
[1612241367.034260] info     | UDPv4AgentLinux.cpp | init                     | running...             | port: 2018

ESP32CAM側プログラム

あらかじめArduino IDEでros2arduinoライブラリをインストールしておきます。

f:id:tshell:20210331214547p:plain

Arduino IDEで以下のプログラムを作成し,ESP32へ転送します。#define AGENT_IPにはMicro XRCE-DDS Agentを動作させているホストのIPアドレスを指定します。

#include <ros2arduino.h>

#include <WiFi.h>
#include <WiFiUdp.h>
#define SSID       "ssid"
#define SSID_PW    "password"
#define AGENT_IP   "ipaddress ex.192.168.0.10"
#define AGENT_PORT 2018 //AGENT port number

#define PUBLISH_FREQUENCY 1 //hz

#define LED_BUILTIN 4

void publishString(std_msgs::String* msg, void* arg)
{
  (void)(arg);

  static int cnt = 0;
  sprintf(msg->data, "Hello ros2arduino %d", cnt++);
}

void subscribeLed(std_msgs::Bool* msg, void* arg)
{
  (void)(arg);

  digitalWrite(LED_BUILTIN, msg->data);
}

class StringPub : public ros2::Node
{
public:
  StringPub()
  : Node("ros2arduino_pub_node")
  {
    ros2::Publisher<std_msgs::String>* publisher_ = this->createPublisher<std_msgs::String>("arduino_chatter");
    this->createWallFreq(PUBLISH_FREQUENCY, (ros2::CallbackFunc)publishString, nullptr, publisher_);

    this->createSubscriber<std_msgs::Bool>("arduino_led", (ros2::CallbackFunc)subscribeLed, nullptr);
  }
};

WiFiUDP udp;

void setup() 
{
  Serial.begin(115200);
  Serial.setDebugOutput(true);
  Serial.println();
  
  pinMode(LED_BUILTIN, OUTPUT);
  
  WiFi.begin(SSID, SSID_PW);
  while(WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("WiFi Connected. IP address : ");
  Serial.println(WiFi.localIP());

  ros2::init(&udp, AGENT_IP, AGENT_PORT);
  Serial.println("Setup completed.");
}

void loop() 
{
  static StringPub StringNode;

  ros2::spin(&StringNode);
}

ESP32CAMとMicro XRCE-DDS Agentの接続

上記のプログラムを書き込んだESP32CAMを起動すると,Micro XRCE-DDS AgentにESP32CAMが接続され以下のようなメッセージが表示されます。

root@83535a5f72b3:/work/Micro-XRCE-DDS-Agent/build# ./MicroXRCEAgent udp4 -p 2018
Press CTRL+C to exit
[1612242146.336194] info     | UDPv4AgentLinux.cpp | init                     | running...             | port: 2018
[1612242160.276226] info     | Root.cpp           | create_client            | create                 | client_key: 0xAABBCCDD, session_id: 0x81
[1612242160.279092] info     | SessionManager.hpp | establish_session        | session established    | client_key: 0x2864434397, address: 172.17.0.1:8331
[1612242160.281801] info     | SessionManager.hpp | establish_session        | session re-established | client_key: 0x2864434397, address: 172.17.0.1:8331
[1612242160.281854] info     | SessionManager.hpp | establish_session        | session re-established | client_key: 0x2864434397, address: 172.17.0.1:8331
[1612242160.281866] info     | SessionManager.hpp | establish_session        | session re-established | client_key: 0x2864434397, address: 172.17.0.1:8331
[1612242160.281873] info     | SessionManager.hpp | establish_session        | session re-established | client_key: 0x2864434397, address: 172.17.0.1:8331
[1612242160.281880] info     | SessionManager.hpp | establish_session        | session re-established | client_key: 0x2864434397, address: 172.17.0.1:8331
[1612242160.281887] info     | SessionManager.hpp | establish_session        | session re-established | client_key: 0x2864434397, address: 172.17.0.1:8331

ROS2との接続

Micro XRCE-DDS Agentを起動するために作成したDockerイメージにはROS2 Dashingの環境も含まれています。以下のようにしてros2 topic listを実行し,arduino_chatterとarduino_ledが表示されることを確認します。

> docker run --rm -it --net host micro-xrce-dds-agent:v1.3.0
# ros2 topic list
/arduino_chatter
/arduino_led
/parameter_events
/rosout

ros2 topic echo arduino_chatterを実行すると以下のようにESP32CAMからPublishされたメッセージが表示されます。

root@docker-desktop:/work# ros2 topic echo arduino_chatter
data: Hello ros2arduino 257
---
data: Hello ros2arduino 258
---
data: Hello ros2arduino 259
---
data: Hello ros2arduino 260
---
data: Hello ros2arduino 261
---
data: Hello ros2arduino 262
---
data: Hello ros2arduino 263
---

また,ros2 topic pub arduino_led std_msgs/msg/Bool '{data: true}'を実行するとESP32CAM上に実装されているLEDが点灯します。ros2 topic pub arduino_led std_msgs/msg/Bool '{data: false}'を実行すると消灯します。

root@docker-desktop:/work# ros2 topic pub arduino_led std_msgs/msg/Bool '{data: true}'
publisher: beginning loop
publishing #1: std_msgs.msg.Bool(data=True)

publishing #2: std_msgs.msg.Bool(data=True)

f:id:tshell:20210331214452j:plain

Raspberry Pi 4 + Ubuntu 20.04 + ROS2 foxyでルンバを動かす

f:id:tshell:20210314211444g:plain

ラズパイでルンバを動かす話はググるとたくさん出てきますが,ラズパイ上でROS2ノードをビルドしようとすると途中で止まったりやたらと時間がかかったりとハマりどころが多かったので以下にまとめます。

手順

以下の手順で進めます。

  1. ソースのダウンロード
  2. qemu-user-staticでビルド
  3. Raspberry Pi 4に転送する
  4. 動作確認

ルンバ用ROS2ノードのダウンロード

以下のソースをcreate_ws/srcにダウンロードします。

$ mkdir -p create_ws/src
$ cd create_ws/src
$ git clone -b foxy https://github.com/AutonomyLab/create_robot.git
$ git clone https://github.com/AutonomyLab/libcreate.git

qemu-user-staticを使ったDockerコンテナでビルドする

tshell.hatenablog.com

前回の記事で作成したRaspberry PiUbuntu 20.04をx86 PC上で動かすコンテナを起動し,その中でビルドします。

まずは先ほどダウンロードしたソースがあるcreate_wsをマウントしてコンテナを起動します。

$ docker run -it -v $(pwd)/create_ws:/home/ubuntu/create_ws --name raspi raspi-ubuntu-ros2:foxy /bin/bash
# adduser ubuntu
# usermod -a -G sudo ubuntu
# chmod ubuntu:ubuntu /home/ubuntu
# su ubuntu
$ cd ~/create_ws

libcreateの依存パッケージをインストールします。

$ sudo apt update
$ sudo apt install build-essential cmake libboost-system-dev libboost-thread-dev

create_robotの依存パッケージをインストールします。

$ source /opt/ros/foxy/setup.bash
$ sudo apt install python3-rosdep
$ cd ~/create_ws
$ sudo rosdep init
$ rosdep update
$ rosdep install --from-paths src -i

ビルドします。

$ colcon build

なにかエラーが出ています。

Summary: 6 packages finished [8min 15s]
  1 package had stderr output: libcreate

libcreateのstderr.logを見てみるとtestのビルドに入ったメッセージが出ているだけで特にエラーではないようなのでこのまま続行します。

$ cat log/latest_build/libcreate/stderr.log
GTest installation found. Building tests.

Raspberry Pi 4へ転送

ビルドしたファイルをtarに固めてRaspberry Pi 4へ転送します。
Raspberry Pi 4には予め/home/ubuntu/create_wsディレクトリを作成しておきます。

$ cd ~/create_ws
$ tar cf ../create_ws.tar .
$ cd ..
$ scp create_ws.tar ubuntu@<Raspberry Pi 4のIPアドレス>:~/create_ws

Raspberry Pi 4にSSH接続し,先ほど転送したtarファイルを展開します。

$ ssh ubuntu@<Raspberry Pi 4のIPアドレス>
$ cd create_ws
$ tar xvf create_ws.tar

ros-foxy-xacroをインストール後,動作確認します。

$ sudo apt install ros-foxy-xacro
$ source instal/setup.bash
$ ros2 launch create_bringup create_2.launch

デフォルトでは/dev/ttyUSB0を使ってルンバと通信しようとします。USB - シリアル変換器を接続していないと以下のようなエラーが発生します。

[create_driver-1]   what():  open: No such file or directory

ルンバと接続するシリアルポートを変更するには create_ws/install/create_bringup/share/create_bringup/config/default.yamldev:行を変更します。

create_driver:
  ros__parameters:
    # The device path for the robot
    dev: "/dev/ttyAMA2"
...

Raspberry Pi 4で複数のUARTを起動する方法は以下の記事に書いています。

tshell.hatenablog.com

動作確認

ルンバと接続しない状態で起動してみます。

$ ros2 launch create_bringup create_2.launch
...
[create_driver-1] [create::Create] retrying to establish serial connection...
[create_driver-1] [create::Serial] serial error - Operation canceled
[create_driver-1] [create::Serial] failed to receive data from Create. Check if robot is powered!
[create_driver-1] [create::Create] retrying to establish serial connection...
[create_driver-1] [create::Serial] serial error - Operation canceled
...

今度はcreate_driverノードが起動してルンバと接続を試みています。

ルンバと接続

f:id:tshell:20210314211552j:plain

ルンバのシリアルポートとRaspberry Pi 4のUARTを直結してos2 launch create_bringup create_2.launchを実行すると以下のようなメッセージが表示され,ルンバと通信できていることが確認できます。
本当はルンバのシリアルポートとRaspberry Pi 4のUARTは信号レベルが違うのでレベル変換回路を入れる必要がありますが,直結でも動きます。

[create_driver-1] [INFO] [1615607466.306440053] [create_driver]: [CREATE] Connection established.
[create_driver-1] [INFO] [1615607466.306769267] [create_driver]: [CREATE] Battery level 90.95 %
[create_driver-1] [INFO] [1615607466.350583726] [create_driver]: [CREATE] Ready.

ここまで来ればcmd_velにPublishすることでルンバを動かすことができます。

ジョイスティック操作

XBOXのコントローラを使ってルンバを動かしてみます。
XBOXのコントローラをPCに接続したら以下のコマンドでteleop_twist_joyを起動します。

$ ros2 launch teleop_twist_joy teleop-launch.py joy_config:='xbox'

LRボタンを同時押ししながら左スティックを操作するとルンバが動きます。

Raspberry Pi 3 + Ubuntu 20.04 + ROS2 foxy向けクロスコンパイル環境をDockerで構築する

f:id:tshell:20210308220454g:plain

tshell.hatenablog.com

前回の記事では動作確認用のROS2ノードをPythonで書いていましたが,実はrt-net / raspimouse2のソースをRaspberry Pi 3でビルドしようとするとraspimouseパッケージのビルドが25%で止まってしまっていたからでした。

別の環境ならうまく行くかも,ということでRaspberry Pi のROS2で動作するノードをC++で記述してx86 PCでビルドする環境を作ります。

qemu-user-staticでARM用のバイナリをx86命令へ変換しながら実行する仕組みを利用して,Raspberry Pi用のUbunru 20.04イメージをx86 PCで動作させます。その上にROS2をインストールして,あたかもPC上でRaspberry Pi用のROS2が動いているかのように見せかけます。環境はDockerで構築します。

手順

以下の手順でクロスコンパイル環境を構築します。

  1. DockerでARM用Ubuntu 20.04を動かす
  2. イメージをtarに固めてdocker importする
  3. インポートしたDockerイメージをもとにROS2環境を構築する

DockerでARM用Ubuntu 20.04を動かす

Raspberry PiUbuntu 20.04 LTSをインストールするときに使用したイメージファイルをホストPCでマウントし,qemu-arm-staticを/usr/binへコピーします。

まずはイメージファイルからループバックデバイスを作成します。

$ sudo losetup -fP ubuntu-20.04.2-preinstalled-server-arm64+raspi.img
$ dmesg | grep loop
[    0.113010] Calibrating delay loop (skipped), value calculated using timer frequency.. 5799.77 BogoMIPS (lpj=11599544)
[    0.631321] loop: module loaded
[ 1644.919981] EXT4-fs (loop20): mounted filesystem with ordered data mode. Opts: (null)
[ 2827.012874] EXT4-fs (loop20): mounted filesystem with ordered data mode. Opts: (null)
[14031.188080]  loop20: p1 p2

/dev/loop20に作成されたようです。

$ sudo fdisk -l /dev/loop20
ディスク /dev/loop20: 3.4 GiB, 3259499520 バイト, 6366210 セクタ
単位: セクタ (1 * 512 = 512 バイト)
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
ディスクラベルのタイプ: dos
ディスク識別子: 0x4ec8ea53

デバイス      起動 開始位置 最後から  セクタ サイズ Id タイプ
/dev/loop20p1 *        2048   526335  524288   256M  c W95 FAT32 (LBA)
/dev/loop20p2        526336  6366175 5839840   2.8G 83 Linux

loopデバイスが作成されており,ubuntu 20.04のイメージに含まれるLinuxパーティション/dev/loop20p2になっています。

ローカル環境に余計なものを入れたくないのでDockerコンテナを立ち上げ,適当なところ(ここでは/mnt)にマウントして作業します。

$ docker run --rm -it --privileged -v $(pwd):/home --name ubuntu ubuntu:20.04
# mount /dev/loop20p2 /mnt

作業用のコンテナ内でqemu-user-staticをインストールします。

# apt update
# apt install -y qemu-user-static

qemu-user-staticでインストールされたコンテナ内の/usr/bin/qemu-arm-staticをイメージファイルの/usr/binにコピーします。

# cp /usr/bin/qemu-arm-static /mnt/usr/bin

修正したイメージファイルをtarに固めます。

# cd /mnt
# tar cf /home/raspi-ubuntu-20.04.tar .

コンテナを終了し,作成したtarファイルのオーナーを変更しておきます。

$ sudo chown ユーザー名:ユーザー名 raspi-ubuntu-20.04.tar

tarファイルをDockerにインポートします。

$ docker import raspi-ubuntu-20.04.tar raspi-ubuntu:20.04

インポートしたイメージを起動します。

$ docker run -it --name raspi-ubuntu raspi-ubuntu:20.04 /bin/bash
root@143d831747f9:/# arch
aarch64

アーキテクチャはaarch64になっており,ARM用のUbuntu 20.04イメージが実行されています。

standard_init_linux.go:211: exec user process caused "exec format error"というエラーが出たら以下を実行します。

$ docker run --rm --privileged multiarch/qemu-user-static --reset -p yes

ROS2のインストール

raspi-ubuntu:20.04イメージが作成できたら以下のようなDockerfileを作成してビルドします。

FROM raspi-ubuntu:20.04

RUN apt update && apt install -y curl gnupg2 lsb-release
RUN curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | apt-key add -
RUN sh -c 'echo "deb [arch=$(dpkg --print-architecture)] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2-latest.list'

RUN apt update && \
    apt install -y ros-foxy-ros-base  python3-colcon-common-extensions python3-pip && \
    pip3 install -U argcomplete
$ docker build --rm -t raspi-ubuntu-ros2:foxy .

ビルドが完了したらコンテナを起動してみます。

$ docker run --rm -it raspi-ubuntu-ros2:foxy /bin/bash

ROS2のexampleを動かしてみて,ちゃんとROS2がインストールされていることを確認します。

raspimouse2のビルド

前回はできなかったraspimouse2のビルドをしてみます。
ホストPCでrt-net / raspimouse2のソースをcloneします。

$ mkdir -p ros2_ws/src
$ cd ros2_ws/src
$ git clone https://github.com/rt-net/raspimouse2.git

cloneしたソースをマウントしてコンテナを起動し,colcon buildを実行します。

$ docker run --rm -it -v $(pwd)/ros2_ws:/home raspi-ubuntu-ros2:foxy /bin/bash
# source /opt/ros/foxy/setup.bash
# cd /home
# colcon build
Starting >>> raspimouse_msgs
Finished <<< raspimouse_msgs [12.2s]                     
Starting >>> raspimouse
[Processing: raspimouse]                             
[Processing: raspimouse]                                      
[Processing: raspimouse]                                       
Finished <<< raspimouse [1min 54s]                             

Summary: 2 packages finished [2min 8s]

正常にビルドできました。

動作確認

rt-net / raspimouse2のREADMEのQuick Startにしたがって動作確認します。

Raspberry Pi MouseにSSH接続し,以下のコマンドを実行します。

$ source ~/ros2_ws/install/setup.bash
$ ros2 run raspimouse raspimouse

別のターミナルでRaspberry Pi MouseにSSH接続し,以下のコマンドを実行します。

$ source /opt/ros/foxy/setup.bash
$ ros2 lifecycle set raspimouse activate

activateすると冒頭のgifのように光センサのLEDが点滅し始めます。
raspimouse2のソースをx86 PCでビルドして,Raspberry Pi Mouseを動かすことができました。

Raspberry Pi MouseをUbuntu 20.04 + ROS2 foxyで動かす

f:id:tshell:20210307222509g:plain

年度末になると掃除したり引き継いだりで面白いモノが出てきたりします。今年はその辺の棚からRaspberry Pi Mouseが出てきたのでUbuntu 20.04とROS2 foxyで動かしてみました。

手順

以下のような手順です。

  1. Raspberry PiUbuntu 20.04 LTSをインストール
  2. ROS2 foxyのインストール
  3. Raspberry Pi Mouseデバイスドライバのインストール
  4. テスト用ROS2ノードの作成
  5. テスト走行

環境

Raspberry PiUbuntu 20.04 LTSをインストールする

Ubuntu 20.04 LTS イメージのダウンロードとMicro SDへの書き込み

Install Ubuntu on a Raspberry PiからARM 64bit用イメージをダウンロードし, 以下のコマンドで解凍します。

$ xz -dv ubuntu-20.04.2-preinstalled-server-arm64+raspi.img.xz

解凍したイメージを以下のコマンドでMicroSDカードに書き込みます。(MicroSDカードが/dev/mmcblk0として認識されている場合)

$ sudo dd if=ubuntu-20.04.2-preinstalled-server-arm64+raspi.img of=/dev/mmcblk0 bs=4M oflag=direct status=progress

無線LAN設定

イメージを書き込んだらMicroSDカードをマウントし,初回起動時から無線LANに接続できるように設定しておきます。
system-boot/network.configを以下のように編集します。

# This file contains a netplan-compatible configuration which cloud-init
# will apply on first-boot. Please refer to the cloud-init documentation and
# the netplan reference for full details:
#
# https://cloudinit.readthedocs.io/
# https://netplan.io/reference
#
# Some additional examples are commented out below

version: 2
ethernets:
  eth0:
    dhcp4: true
    optional: true
wifis:
  wlan0:
    dhcp4: true
    optional: true
    access-points:
      myhomewifi:
        password: "S3kr1t"
#      myworkwifi:
#        password: "correct battery horse staple"
#      workssid:
#        auth:
#          key-management: eap
#          method: peap
#          identity: "me@example.com"
#          password: "passw0rd"
#          ca-certificate: /etc/my_ca.pem

起動

上記の設定が終わったらMicro SDカードをRaspberry Piに挿入して起動します。IPアドレスDHCPで取得するように設定したのでRaspberry PiIPアドレスを以下のコマンドにより調査します。
例では192.168.0.30に設定されています。

$ nmap -sP 192.168.0.0/24
Starting Nmap 7.80 ( https://nmap.org ) at 2021-03-05 21:04 JST
Nmap scan report for _gateway (192.168.0.1)
...
Nmap scan report for 192.168.0.30
...
Nmap done: 256 IP addresses (4 hosts up) scanned in 3.71 seconds

Raspberry PiIPアドレスを調査したらsshで接続します。

$ ssh ubuntu@192.168.0.30

ROS2 foxyのインストール

Installing ROS 2 via Debian Packagesの手順に従って,以下のコマンドを実行してROS2 foxyをインストールします。

$ sudo apt update && sudo apt install curl gnupg2 lsb-release
$ curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -
$ sudo sh -c 'echo "deb [arch=$(dpkg --print-architecture)] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2-latest.list'
$ sudo apt update
$ sudo apt install ros-foxy-ros-base
$ sudo apt install python3-colcon-common-extensions
$ sudo apt install -y python3-pip
$ pip3 install -U argcomplete

Raspberry Pi Mouseデバイスドライバのインストール

rt-net/RaspberryPiMouse のインストール手順にしたがってデバイスドライバをインストールします。

$ git clone https://github.com/rt-net/RaspberryPiMouse.git
$ cd RaspberryPiMouse/utils
$ sudo apt install linux-headers-$(uname -r) build-essential
$ ./build_install.bash

/dev/rtlightsensor0の値を表示して動作確認します。

ubuntu@ubuntu:~/raspimouse/RaspberryPiMouse/utils$ cat /dev/rtlightsensor0
24 1 2 35

車体右側のセンサ直前にものを置いたとき

ubuntu@ubuntu:~/raspimouse/RaspberryPiMouse/utils$ cat /dev/rtlightsensor0
26 2 3 461

値が変化していることがわかります。正常に動作しているようです。

再起動した際には以下コマンドによりドライバを読み込ませる必要があります。

$ cd ~/raspimouse/RaspberryPiMouse/src/drivers
$ sudo insmod rtmouse.ko
$ sudo chmod 666 /dev/rt*
$ echo 0 > /dev/rtmotoren0

起動時にrtmouse.koを読み込む

上記のコマンドを毎回実行するのは面倒なので起動時に自動実行されるようにします。

/usr/local/binに以下の内容のload-rtmouse.shを作成します。

#!/bin/bash

insmod /home/ubuntu/raspimouse/RaspberryPiMouse/src/drivers/rtmouse.ko
chmod 666 /dev/rt*
echo 0 > /dev/rtmotoren0

load-rtmouse.shのパーミッションを変更しておきます。

$ sudo chmod 744 /usr/local/bin/load-rtmouse.sh

また,/etc/systemd/systemにload-rtmouse.serviceを作成します。

[Unit]
After=network.target

[Service]
ExecStart=/usr/local/bin/load-rtmouse.sh

[Install]
WantedBy=default.target

こちらもパーミッションを変更しておきます。

$ sudo chmod 664 /etc/systemd/system/load-rtmouse.service

load-rtmouse.shとload-rtmouse.serviceを作成したら以下のコマンドを実行します。

$ sudo systemctl daemon-reload
$ sudo systemctl enable load-rtmouse.service

テスト用ROS2ノードの作成

ros2 / examplesのpublisher_member_function.pyを改造し,以下のようなノードを作成します。

import rclpy
from rclpy.node import Node

from std_msgs.msg import String
from geometry_msgs.msg import Twist

import math

class MinimalPublisher(Node):

    def __init__(self):
        super().__init__('minimal_publisher')
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 0

        self.subscriber_ = self.create_subscription(Twist, 'cmd_vel', self.velocity_command, 10)
        self.left_motor_control_ = open('/dev/rtmotor_raw_l0', 'w')
        self.right_motor_control_ = open('/dev/rtmotor_raw_r0', 'w')

    def timer_callback(self):
        msg = String()
        msg.data = 'Hello World: %d' % self.i
        self.publisher_.publish(msg)
        self.get_logger().info('Publishing: "%s"' % msg.data)
        self.i += 1
    
    def velocity_command(self, msg):
        linear_velocity_ = msg.linear.x
        angular_velocity_ = msg.angular.z

        self.get_logger().info('cmd_vel linear: "%s"' % linear_velocity_)
        self.get_logger().info('cmd_vel angular: "%s"' % angular_velocity_)

        forward_hz = 80000 * linear_velocity_ / (9 * math.pi)
        rotation_hz = 400 * angular_velocity_ / math.pi

        self.left_motor_control_.write(str(round(forward_hz - rotation_hz)))
        self.right_motor_control_.write(str(round(forward_hz + rotation_hz)))
        self.left_motor_control_.flush()
        self.right_motor_control_.flush()


def main(args=None):
    rclpy.init(args=args)

    minimal_publisher = MinimalPublisher()

    # Motor enable
    motorenable = open('/dev/rtmotoren0', 'w')
    motorenable.write('1')
    motorenable.close()
    

    rclpy.spin(minimal_publisher)

    # Destroy the node explicitly
    # (optional - otherwise it will be done automatically
    # when the garbage collector destroys the node object)

    # Motor disable
    motorenable = open('/dev/rtmotoren0', 'w')
    motorenable.write('0')
    motorenable.close()

    minimal_publisher.left_motor_control_.close()
    minimal_publisher.right_motor_control_.close()
    minimal_publisher.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

cmd_velに与えるlinear.xで前後方向速度を,angular.zで旋回速度を指令します。

ROS2でのPythonノード作成方法は割愛します。以下コマンドでテスト用のROS2ノードが起動できるようにしておきます。

$ ros2 run myraspimouse myraspimouse

テスト走行

キーボードで操作できるようにteleop-twist-keyboardをインストールして起動します。

$ sudo apt install ros-foxy-teleop-twist-keyboard
$ source /opt/ros/foxy/setup.bash
$ ros2 run teleop_twist_keyboard teleop_twist_keyboard

キー操作とRaspberry Pi Mouseの動作の対応は以下のようになります。

キー 動作
i 前進
, 後進
j その場左旋回
l その場右旋回
u 左前方に進む
o 右前方に進む
m 左後方に進む
. 右後方に進む

Raspberry Pi 4 + Ubuntu 20.04で複数のUARTを有効にする

UART configurationによるとRaspberry Pi 4では6つのUARTが利用できるはずですが,Ubuntu 20.04からは1つのUARTしか見えません。しかもデフォルトではシリアルコンソールに割り当てられているので,これを使って通信するアプリケーション(例えばルンバを動かすとか)を作るのはやめた方がよさそうです。

ラズパイ4のUARTでルンバを動かしたいので使えるはずのUARTを使えるようにしてみました。

環境

設定ファイルの編集

以下2つのファイルを編集して,まずはUART0(8, 10pin) 以外のUARTを有効にしてみます。

syscfg.txtで有効にするUARTの数を決めて,usercfg.txtでdtoverlayを読み込ませます。

/boot/firmware/syscfg.txtの編集

以下のように``enable_uart‘‘を2に修正します。

# This file is intended to be modified by the pibootctl utility. User
# configuration changes should be placed in "usercfg.txt". Please refer to the
# README file for a description of the various configuration files on the boot
# partition.

enable_uart=2
dtparam=audio=on
dtparam=i2c_arm=on
dtparam=spi=on
cmdline=cmdline.txt

/boot/firmare/usercfg.txtの編集

dtoverlay=uart2 を追加します。

# Place "config.txt" changes (dtparam, dtoverlay, disable_overscan, etc.) in
# this file. Please refer to the README file for a description of the various
# configuration files on the boot partition.

dtoverlay=uart2

UARTが有効になったことの確認

syscfg.txt,usercfg.txtを編集して再起動すると/dev/ttyAMA1が見えるようになります。

ubuntu@ubuntu:~$ dmesg | grep ttyAMA
[    1.654046] fe201000.serial: ttyAMA0 at MMIO 0xfe201000 (irq = 14, base_baud = 0) is a PL011 rev2
[    1.654434] fe201400.serial: ttyAMA1 at MMIO 0xfe201400 (irq = 14, base_baud = 0) is a PL011 rev2

screen コマンドで/dev/ttyAMA1を開いて,通信できることを確認しましょう。 pin27がTx,pin28がRxです。

$ screen /dev/ttyAMA1 9600

screenから抜ける方法はいつも忘れますが,Ctrl + a, kです。

ほかのUARTも有効にする

syscfg.txt,usercfg.txtを以下のように設定すればほかのUARTも使えるようになります。

ubuntu@ubuntu:~$ cat /boot/firmware/syscfg.txt
# This file is intended to be modified by the pibootctl utility. User
# configuration changes should be placed in "usercfg.txt". Please refer to the
# README file for a description of the various configuration files on the boot
# partition.

enable_uart=6
dtparam=audio=on
dtparam=i2c_arm=on
dtparam=spi=on
cmdline=cmdline.txt
ubuntu@ubuntu:~$ cat /boot/firmware/usercfg.txt
# Place "config.txt" changes (dtparam, dtoverlay, disable_overscan, etc.) in
# this file. Please refer to the README file for a description of the various
# configuration files on the boot partition.

dtoverlay=uart2
dtoverlay=uart3
dtoverlay=uart4
dtoverlay=uart5
ubuntu@ubuntu:~$ dmesg | grep ttyAMA
[    1.662514] fe201000.serial: ttyAMA0 at MMIO 0xfe201000 (irq = 14, base_baud = 0) is a PL011 rev2
[    1.662963] fe201400.serial: ttyAMA1 at MMIO 0xfe201400 (irq = 14, base_baud = 0) is a PL011 rev2
[    1.663212] fe201600.serial: ttyAMA2 at MMIO 0xfe201600 (irq = 14, base_baud = 0) is a PL011 rev2
[    1.663448] fe201800.serial: ttyAMA3 at MMIO 0xfe201800 (irq = 14, base_baud = 0) is a PL011 rev2
[    1.663675] fe201a00.serial: ttyAMA4 at MMIO 0xfe201a00 (irq = 14, base_baud = 0) is a PL011 rev2

ピンアサインは以下の通りです。

UART Tx pin Rx pin
/dev/ttyS0 8 10
/dev/ttyAMA0 8 10
/dev/ttyAMA1 27 28
/dev/ttyAMA2 7 29
/dev/ttyAMA3 24 21
/dev/ttyAMA4 32 33

ttyAMA1で使われる27pinはReservedになっており,起動時には何かデータが出力されるようです。ルンバを動かすなど,予期しないデータが出力されると困る場合はほかのUARTを使えばよさそうです。
ttyAMA4でしか確認していませんが,ttyAMA1以外であれば起動時にデータは出力されないはずです。

Application InspectorをDockerで動かす

米Microsoft、ソースコード解析ツール「Application Inspector」を公開とのニュースを見つけたのでApplication InspectorをDockerで動かしてみました。
Alpineをベースにしましたがmcr.microsoft.com/dotnet/core/sdkをベースにしてもイメージのサイズは同じだったのでどちらでもよいと思います。
自分で.NET Core SDKを入れたほうが勉強にはなるかも・・・

環境

以下の環境を使用しました。

  • ubuntu 18.04 LTS
    • kernel 5.3.0-40-generic
  • Docker version 18.09.7, build 2d0083d

Dockerfileの作成

を見ながら 以下のようなDockerfileを作成します。
2つのバージョン間でdiffをとったりできるようなので,ソースをマウントするディレクトリはsrc1とsrc2の2つを作りました。結果は/outputに出力させるようにします。

# docker build --rm -t appinspector:latest .
FROM alpine

# .NET Coreのインストール
RUN apk add --no-cache libstdc++ libintl && \
    mkdir /dotnet && \
    wget https://download.visualstudio.microsoft.com/download/pr/f686a328-a7e2-403c-9107-2e8d8b54aaa9/a1ed4732a9388c5817f79564e3fbbc3a/dotnet-sdk-3.1.102-linux-musl-x64.tar.gz && \
    tar zxf dotnet-sdk-3.1.102-linux-musl-x64.tar.gz -C /dotnet && \
    rm -f dotnet-sdk-3.1.102-linux-musl-x64.tar.gz

ENV DOTNET_ROOT=/dotnet
ENV PATH=$PATH:/dotnet

# Application Inspectorのインストール
WORKDIR /appinspector
RUN apk add --no-cache git icu-libs && \
    git clone https://github.com/microsoft/ApplicationInspector.git && \
    cd ApplicationInspector && \
    dotnet publish && \
    cp -r /appinspector/ApplicationInspector/AppInspector/bin/Debug/netcoreapp3.0/publish /appinspector/publish && \
    rm -fr /appinspector/ApplicationInspector && \
    mkdir /src1 /src2 /output

Dockerfileを作成したらイメージをビルドします。

$ docker  build --rm -t appinspector:latest

使用方法

ApplicationInspectorでApplicationInspectorを解析してみます。

$ git clone https://github.com/microsoft/ApplicationInspector.git
$ cd ApplicationInspector
$ docker run --rm -it -v $(pwd):/src1 -v /home/username:/output appinspector:latest

コンテナが起動したら以下を実行します。

# dotnet publish/ApplicationInspector.dll analyze -s /src1
# cp -r publish/html /output
# cp publish/output.html /output

結果の表示

出力結果のoutput.htmlをブラウザで開くと以下のような画面が表示されます。

f:id:tshell:20200303211155p:plain

下部の青いViewReportボタンをクリックすると以下のような画面が表示され,ファイル構成などがわかります。

f:id:tshell:20200303211214p:plain

上部のFeaturesをクリックすると以下のように検出された項目がカテゴリごとに表示されます。

f:id:tshell:20200303211232p:plain

がめんがきれいでいいとおもいます(小並感

Neo4JをNode.jsから使う

tshell.hatenablog.com

Neo4JをDockerでお手軽に試してみました。
実用的なアプリケーションを作るとなるとPythonやらJavascript(Node.js)などから使えるようにしたいです。
公式ドキュメントのDriver Manualを見ながら,Node.jsでNeo4Jにアクセスしてみます。

環境

以下の環境で試しました。

  • Windows 10 Professional version 1903
  • Node.js 12.10.0

インストール

Driver ManualのGet Started .NETJavaJavascript用ドライバ(Neo4Jアクセス用ライブラリ)のインストール方法が記載されています。
今回はJavascriptを使うので,1.2. Driver versions and installationの中のExample 1.1. Acquire the driverのタブをJavascriptにすると書いてある内容を実行していきます。

以下のコマンドを実行してインストール可能なneo4j-driverの一覧を表示します。

> npm show neo4j-driver@* version

4.0.0をインストールすることにします。

> npm install neo4j-driver@4.0.0

サンプルコードの作成

Get Startedに載っているサンプルは残念ながら動きませんでした。
以下のようなソースをindex.jsとして作成します。

neo4j = require('neo4j-driver');

const uri = 'bolt://localhost';
const user = 'neo4j';
const password = 'neo4j';

const driver = neo4j.driver(uri, neo4j.auth.basic(user, password));
const session = driver.session();

session
.run(
    'CREATE (a:Greeting) SET a.message = $message RETURN a.message + ", from node " + id(a)',
       { message: 'hello, world' }
)
.then(result => {
    session.close();
    const singleRecord = result.records[0];
    const greeting = singleRecord.get(0);

    console.log(greeting);
})
.catch(error =>{
    session.close();
    throw error;
});

以下のコマンドで実行します。

node index.js

Neo4Jへの書き込みに成功すると以下のようなレスポンスが表示されます。

hello, world, from node 343

結果の確認

http://localhost:7474にアクセスして以下コマンドを実行します。 Neo4JのWebインターフェース,コマンド入力部で改行するにはShift + Enterを入力します。

MATCH (a:Greeting)
RETURN a

以下のようにノードが表示されます。ここではindex.jsを2回実行してしまったので2つ表示されています。

f:id:tshell:20200218214559p:plain

WHERE句を使えば指定したidのノードだけ表示させることができます。

MATCH (a:Greeting)
WHERE id(a) = 343
RETURN a

f:id:tshell:20200218214734p:plain