#1.はじめに
Docker を触ったことがなかったので、勉強を兼ねてROS on Docker on Raspberry Piを試してみました。
ここでは、ROSのコンテナからRaspberry Pi 4 (3 B+もOK)のGPIOやI2C、それに関わるライブラリ(RPI.GPIO, WiringPi, Grove.Py, シリアル通信)が使える様にする方法をまとめました。
20/5/17追記:コンテナの中から、シリアル通信を使う場合の注意点を追記しました。
21/1/3追記:コンテナの中からファイルを生成するときに、rootではなくホスト側の一般ユーザにマッピングする方法を追記しました。
##実行環境
ハード | Raspberry Pi 3B+, Raspberry Pi 4B+ |
OS | Raspbian Buster |
※Raspbianのため、32bit環境(armhf)になります。(ROSの推奨は64bit)
#2.Dockerインストール
##(1)Docker公式の手順
RaspberryPiにDockerをインストールする手順は、こちら1を参考にさせて頂きました。公式のインストールドキュメントはこちら2です。
$ sudo apt update && sudo apt upgrade -y
$ sudo apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common -y
$ curl -sSL https://get.docker.com | sh
$ docker -v
Docker version 19.03.7, build 7141c19
$ sudo usermod -aG docker pi
ユーザpi
がグループdocker
に入ったのを反映するため、一旦ログアウトして、再度ログインします。
動作チェックを行います。
$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
4ee5c797bcd7: Pull complete
Digest: sha256:6a65f928fb91fcfbc963f7aa6d57c8eeb426ad9a20c7ee045538ef34847f44f1
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
(・・・省略・・・)
##(2)Raspbianパッケージを使う手順
少し古いですが、Raspbianのパッケージでもインストールできます。(20年5月時点)
$ sudo apt install docker.io
$ docker -v
Docker version 18.09.1, build 4c52b90
##(3)docker-composeビルドとインストール
こちら3を参考にさせて頂きました。
$ mkdir ~/gitwork
$ cd ~/gitwork
$ git clone https://github.com/docker/compose.git
$ cd compose
$ ./script/build/linux
(・・・ビルド、15~30分程度・・・)
$ ./dist/docker-compose-Linux-armv7l version
docker-compose version 1.26.0dev, build 3c89ff84
docker-py version: 4.2.0
CPython version: 3.7.6
OpenSSL version: OpenSSL 1.1.0l 10 Sep 2019
$
動作確認できたら、/usr/local/bin/
に配置します。
$ sudo cp ./dist/docker-compose-Linux-armv7l /usr/local/bin/docker-compose
$ sudo chown root:root /usr/local/bin/docker-compose
$ sudo chmod 755 /usr/local/bin/docker-compose
$ which docker-compose
/usr/local/bin/docker-compose
$
#3.コンテナを起動
GPIO、I2C、シリアル通信を使う時の引数(とその他)です。
$ docker run --name ros2 -h ros2dk -v /home/pi/gitwork:/root/gitwork --device /dev/gpiomem --device /dev/i2c-1 --device /dev/ttyACM0 --privileged -e TZ=Asia/Tokyo -it ros:eloquent
引数 | 意味 |
---|---|
-name ros2 | コンテナの名前をROS2にする |
-h ros2dk | コンテナのホスト名をros2dkにする |
-v <マウント元>:<マウント先> | ホストのディレクトリ(元)をコンテナ内のディレクトリにマウント(先) |
--device /dev/gpiomem | GPIOデバイスに接続 |
--device /dev/i2c-1 | I2Cデバイスに接続 |
--device /dev/ttyACM0 | シリアル通信に接続 |
--privileged | 特権モードで実行(表外に注釈) |
-e TZ=Asia/Tokyo | 環境変数を定義してタイムゾーンを日本に変更 |
-it | -i:標準入力を開き続ける、-t:疑似ttyを割りあて→docker runしたあとそのままコンテナに入れます |
--rm | コンテナ終了時にコンテナを削除(例では指定していません) |
ros:eloquent | イメージ名(2020年2月現在の最新はeloquent) |
###「-v」オプション
Dockerがマウントした先のディレクトリには、ユーザroot
としてファイルが書き出されます。root以外のユーザにする場合は、下記を参考にしました。
またROSのdebugging infoを出力のため、-v "/home/ubuntu/.ros/:/root/.ros/"
のように、.ros
ディレクトリも必要に応じてマウントするのが望ましいです。
##「add_event_detect」と「--privileged」オプションについて
privilegedを有効にすると、コンテナならではの安全性がすっぽりなくなってしまうので、使用が推奨されないオプションです。4
ところが、Python3でRPI.GPIO
を使用する際、GPIOの入力をイベントで取得する設定をする関数GPIO.add_event_detect
を呼び出すと、下記のエラーが発生して、GPIOの入力をイベントで取得出来ませんでした。
GPIO.add_event_detect(PIN, GPIO.BOTH, handle)
RuntimeError: Failed to add edge detection
いろいろ調べる2と、--privilegedを有効にすることでこのエラーを回避出来ました。(add_event_detectを使わない場合は、このオプションは不要です)
#4.コンテナに入って色々インストール
###(参考)コンテナの操作
ホスト側から、以下の操作をします。
コマンド一覧はこちら5を、詳細はこちら6を参考にさせて頂きました。
やりたいこと | コマンド |
---|---|
コンテナの起動 | $ docker start ros2 |
コンテナのプロセス確認 | $ docker ps |
コンテナの一覧(停止中含む) | $ docker ps -a |
コンテナに入る | $ docker attach ros2 |
コンテナに入る(別ttyで接続) | $ docker exec -it ros2 bash |
一時的にコンテナから抜ける(コンテナは動作しっぱなし) | (コンテナの中のシェルから)# [Ctrl-P] [Ctrl-Q] |
コンテナを停止する | (コンテナの中のシェルから)# exit |
コンテナを停止する | $ docker stop ros2 |
コンテナを削除する | $ docker rm <コンテナID> |
##(1)RPI.GPIO
# cd /var/gitwork
# apt update
# apt install python3-rpi.gpio -y
# i2cdetect -r -y 1
##(2)I2C
# apt install i2c-tools python3-smbus -y
# i2cdetect -r -y 1
###I2Cの疎通確認
RTC(DS1307+ →0x68)とIOエキスパンダー(MCP23017-E/SP →0x20)が繋がってる例です
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: 20 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- 68 -- -- -- -- -- -- --
##(3)WiringPi
途中、色々ワーニングが出ますが、インストールは出来ます。
RaspberryPi4であっても、WiringPiを差し替える必要はありません。
(参考)
# cd /var/gitwork
# git clone https://github.com/WiringPi/WiringPi
# cd WiringPi
# ./build
wiringPi Build script
=====================
WiringPi Library
[UnInstall]
[Compile] wiringPi.c
[Compile] piHiPri.c
・・・(省略)
###GPIOの確認
# gpio -v
gpio version: 2.60
Copyright (c) 2012-2018 Gordon Henderson
This is free software with ABSOLUTELY NO WARRANTY.
For details type: gpio -warranty
Raspberry Pi Details:
Type: Pi 3B+, Revision: 03, Memory: 1024MB, Maker: Sony
* This Raspberry Pi supports user-level GPIO access.
# watch -n 1 gpio readall
+-----+-----+---------+------+---+---Pi 3B+-+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| | | 3.3v | | | 1 || 2 | | | 5v | | |
| 2 | 8 | SDA.1 | ALT0 | 1 | 3 || 4 | | | 5v | | |
| 3 | 9 | SCL.1 | ALT0 | 1 | 5 || 6 | | | 0v | | |
| 4 | 7 | GPIO. 7 | IN | 1 | 7 || 8 | 1 | ALT5 | TxD | 15 | 14 |
| | | 0v | | | 9 || 10 | 1 | ALT5 | RxD | 16 | 15 |
| 17 | 0 | GPIO. 0 | IN | 0 | 11 || 12 | 0 | IN | GPIO. 1 | 1 | 18 |
| 27 | 2 | GPIO. 2 | IN | 0 | 13 || 14 | | | 0v | | |
| 22 | 3 | GPIO. 3 | IN | 0 | 15 || 16 | 0 | IN | GPIO. 4 | 4 | 23 |
| | | 3.3v | | | 17 || 18 | 0 | IN | GPIO. 5 | 5 | 24 |
| 10 | 12 | MOSI | ALT0 | 0 | 19 || 20 | | | 0v | | |
| 9 | 13 | MISO | ALT0 | 0 | 21 || 22 | 0 | IN | GPIO. 6 | 6 | 25 |
| 11 | 14 | SCLK | ALT0 | 0 | 23 || 24 | 1 | OUT | CE0 | 10 | 8 |
| | | 0v | | | 25 || 26 | 1 | OUT | CE1 | 11 | 7 |
| 0 | 30 | SDA.0 | IN | 1 | 27 || 28 | 1 | IN | SCL.0 | 31 | 1 |
| 5 | 21 | GPIO.21 | IN | 1 | 29 || 30 | | | 0v | | |
| 6 | 22 | GPIO.22 | IN | 0 | 31 || 32 | 0 | IN | GPIO.26 | 26 | 12 |
| 13 | 23 | GPIO.23 | IN | 0 | 33 || 34 | | | 0v | | |
| 19 | 24 | GPIO.24 | IN | 0 | 35 || 36 | 0 | IN | GPIO.27 | 27 | 16 |
| 26 | 25 | GPIO.25 | IN | 0 | 37 || 38 | 0 | IN | GPIO.28 | 28 | 20 |
| | | 0v | | | 39 || 40 | 0 | IN | GPIO.29 | 29 | 21 |
+-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
| BCM | wPi | Name | Mode | V | Physical | V | Mode | Name | wPi | BCM |
+-----+-----+---------+------+---+---Pi 3B+-+---+------+---------+-----+-----+
#GPIOの出力動作チェック
~ $ gpio -g mode 6 out ; gpio -g mode 13 out ; gpio -g mode 19 out ; gpio -g mode 26 out
~ $ gpio -g write 6 1 ; gpio -g write 13 1 ; gpio -g write 19 1 ; gpio -g write 26 1
~ $ gpio -g write 6 0 ; gpio -g write 13 0 ; gpio -g write 19 0 ; gpio -g write 26 0
##(4)Grove.py
Seeed社から販売されている、GROVE規格のコネクタを増設するボードです。
すごく使いやすいので、便利に活用してます。
http://wiki.seeedstudio.com/Grove_Base_Hat_for_Raspberry_Pi/
# cd /var/gitwork
# git clone https://github.com/Seeed-Studio/grove.py
Cloning into 'grove.py'...
remote: Enumerating objects: 5, done.
・・・(省略)・・・
# cd grove.py/
# pip3 install .
Processing /root/download/grove.py
Requirement already satisfied: RPi.GPIO in /usr/lib/python3/dist-packages (from grove.py==0.6)
Collecting rpi_ws281x (from grove.py==0.6)
Downloading https://files.pythonhosted.org/packages/3b/b3/cdc84887ead3f15b9f3f2b5fe2b48168757a606c6d08feb53536a8afb396/rpi_ws281x-4.2.2.tar.gz (65kB)
100% |████████████████████████████████| 71kB 1.4MB/s
Collecting smbus2 (from grove.py==0.6)
・・・(省略)・・・
Successfully built rpi-ws281x smbus2
Installing collected packages: rpi-ws281x, smbus2, grove.py
Running setup.py install for grove.py ... done
Successfully installed grove.py-0.6 rpi-ws281x-4.2.2 smbus2-0.3.0
##(5)USB経由のシリアル通信
RaspberryPiのUSBコネクタにArduinoを接続して、USB経由で通信78することを想定します。
まずは、ターミナルのcu
を使って通信してみます。
root@rosdk:~/# apt install cu -y
root@rosdk:~/# cu -s 115200 -l /dev/ttyACM0
cu: open (/dev/ttyACM0): Permission denied
cu: /dev/ttyACM0: Line in use
接続できません。
処置の方法9を探したところ、コンテナ内の/dev/ttyACM0
のパーミッションの変更で接続出来るようです。
#現状のパーミッションを確認
root@rosdk:~/# ls -l /dev/ttyAC*
crw-rw---- 1 root dialout 166, 0 May 18 01:01 /dev/ttyACM0
#その他ユーザの読み書き権限を追加
root@rosdk:~/# chmod 666 /dev/ttyACM0
#追加後のパーミッションを確認
root@rosdk:~/# ls -l /dev/ttyAC*
crw-rw-rw- 1 root dialout 166, 0 May 18 01:01 /dev/ttyACM0
#cuコマンドを実行
root@rosdk:~/gitwork# cu -s 115200 -l /dev/ttyACM0
Connected.
~[rosdk].
※cuコマンドの終了は、"~"を入力してから"."を入力します。
また別の資料10では、docker内のユーザについての言及がありました。
コンテナ内の/dev/ttyACM0
のオーナはそもそもroot
なので、問題無いだろうと思ったのですが、
試しにコンテナ内のdialout
グループにroot
を加えてみました。
#その他ユーザの読み書き権限を一旦削除
root@rosdk:~/# chmod 660 /dev/ttyACM0
#dialoutグループにrootを加える
root@rosdk:~/# sudo usermod -aG dialout root
#一旦コンテナを止めてから、もう一度立ち上げる
root@rosdk:~/# exit
exit
pi@raspi4:~ $ docker start ros1
ros1
#コンテナに入る
pi@raspi4:~ $ docker attach ros1
#ユーザのIDを確認。dialoutグループに入っている。
root@rosdk:~/gitwork# id
uid=0(root) gid=0(root) groups=0(root),20(dialout)
root@rosdk:~/# sudo usermod -aG dialout root
#cuコマンドを実行
root@rosdk:~/# cu -s 115200 -l /dev/ttyACM0
Connected.
~[rosdk].
#pyserialを試す
>>> import serial
>>> ser = serial.Serial('/dev/ttyACM0', 115200)
>>> ser.write("w")
1
>>> ser.write("e")
1
>>> [Ctrl-D]
というわけで、シリアル通信を使うには、コンテナ内のroot
ユーザを、dialout
グループに追加する必要があるようです。
(うーん、何でだろう・・・次章のDockerfileには、groupaddを使う方で追記してます)
#5.Dockerfileを試してみる
ここまでの一連の作業を、Dockerfileにまとめて記述して、イメージを作ります。
##(1)Dockerfile
ROS1 melodicでの例です。
python2系列になるので、インストールパッケージを注意して下さい。(python-*, pip)
$ mkdir workdir; cd workdir
$ touch Dockerfile
for ROS1
# image
FROM ros:melodic
# Set default work directory
WORKDIR /var/workdir
# install packages
# GPIO, I2C, Serial (for Python2.x)
RUN set -x &&\
apt-get update &&\
apt-get install \
python-rpi.gpio i2c-tools python-smbus cu -y
# WiringPi Install
RUN set -x &&\
git clone https://github.com/WiringPi/WiringPi &&\
cd WiringPi &&\
./build
# grove.py (for Python2.x pip)
RUN set -x &&\
git clone https://github.com/Seeed-Studio/grove.py &&\
cd grove.py/ &&\
pip install .
# Add group
RUN set -x &&\
usermod -aG dialout root
for ROS2
ROS2系では、python3系列をインストールして下さい。(python3-*, pip3)
# image
FROM ros:eloquent
# Set default work directory
WORKDIR /var/workdir
# install packages
# GPIO, I2C, Serial (for Python3.x)
RUN set -x &&\
apt-get update &&\
apt-get install \
python3-rpi.gpio i2c-tools python3-smbus cu -y
# WiringPi Install
RUN set -x &&\
git clone https://github.com/WiringPi/WiringPi &&\
cd WiringPi &&\
./build
# grove.py (for Python2.x pip)
RUN set -x &&\
git clone https://github.com/Seeed-Studio/grove.py &&\
cd grove.py/ &&\
pip3 install .
# Add group
RUN set -x &&\
usermod -aG dialout root
##(2)イメージをビルド
Dockerfileを置いてあるディレクトリにいる状態で、下記を実行。
$ docker build -t rosmine .
-t
の引数(例:rosmine
)は、自由に付けてOKです。
##(3)コンテナを起動
前述と同じですが、イメージ名が変わります。(例:rosmine
)
下記のコマンド例では、コンテナ名はros1
にしてます。
$ docker run --name ros1 -h rosdk -v /home/pi/gitwork:/root/gitwork --device /dev/gpiomem --device /dev/i2c-1 --device /dev/ttyACM0 --privileged --rm -e TZ=Asia/Tokyo -it rosmine
#5.ユーザマッピング
コンテナの中から、-v
オプションで接続したディレクトリに読み書きすると、ファイルのオーナーがroot
になってしまいます。
ここでは、userns-remap
という仕組みをつかって、生成したファイルがroot→ホスト側の一般ユーザがオーナーになるようにしてみます。
なお、後述の注意書きのとおりの問題がありますので、ros2の関連ファイルを生成するときなどにこの方法を使って下さい。
注意
この方法でコンテナをつくると、--device
や--privileged
といった、ホスト側でroot権限の必要なオプションを付けることができません。(有効にして立ち上げると、今度はユーザマッピングができなくなります)
ユーザマッピングと両方成り立つような方法を探りましたが、今の所良い方法が無く、コンテナを切り替えながら使う事になります。
くわしくは、こちらの記事を参考下さい。
##(1)事前の設定
/etc/docker/daemon.json
に追記。
(ファイルが無ければ新規に作ってOK)
$ sudo nano /etc/docker/daemon.json
{
"runtimes": {
"nvidia": {
"path": "nvidia-container-runtime",
"runtimeArgs": []
}
}
,"userns-remap": "default"
### ↑ここを追記。先頭のコンマは、これより前の行に設定が入っているときに必要です
}
ここで一旦、Dockerを再起動します。
再起動によって、この後編集するファイルに、自動的にひな形が追加されます。
$ sudo systemctl restart docker.service
続いて2つのファイルを編集します。
ユーザID/グループIDを指定の数だけオフセットするので、その数を指定します。
dockremap~
というキーワードは、userns-remap
が自動的に参照するので、そのまま使います。
$ sudo nano /etc/subuid
編集前
dockremap:100000:65536
編集後
dockremap:1000:65536
ホストOS側の自分のUIDが1000の筈ですので、その値に修正します。
続いてもう一つのファイル。
$ sudo nano /etc/subgid
編集前
dockremap:100000:65536
編集後
dockremap:1000:65536
ホストOS側の自分のGIDが1000の筈ですので、その値に修正します。
ここでDockerを再起動します。
$ sudo systemctl restart docker.service
##(2)コンテナを起動
ros2user
という名前でコンテナを起動します。
前章で紹介の--device
や--privileged
は外しています。
$ docker run --name ros2user -h rosdk -v /home/myasu/gitwork:/root/gitwork -w /root/gitwork --rm -e TZ=Asia/Tokyo -it rosmine
##(3)プロジェクトを作る
プロジェクトを作ってみます。
手順は、こちらを参考にしています。
#作業ディレクトリを作成
root@rosdk:~/gitwork# mkdir -p ./ros2_ws/src
root@rosdk:~/gitwork# cd !$
cd ./ros2_ws/src
#パッケージを作成
root@rosdk:~/gitwork/ros2_ws/src# ros2 pkg create pubsubpy_mes
going to create a new package
package name: pubsubpy_mes
destination directory: /root/gitwork/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['root <root@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
creating folder ./pubsubpy_mes
creating ./pubsubpy_mes/package.xml
creating source and include folder
creating folder ./pubsubpy_mes/src
creating folder ./pubsubpy_mes/include/pubsubpy_mes
creating ./pubsubpy_mes/CMakeLists.txt
コンテナの中から見たオーナーを確認してみます。
root@rosdk:~/gitwork/ros2_ws/src# la -la
total 12
drwxr-xr-x 3 root root 4096 Jan 3 14:10 .
drwxr-xr-x 3 root root 4096 Jan 3 14:09 ..
drwxr-xr-x 4 root root 4096 Jan 3 14:10 pubsubpy_mes
root@rosdk:~/gitwork/ros2_ws/src# cd pubsubpy_mes/
root@rosdk:~/gitwork/ros2_ws/src/pubsubpy_mes# ls -la
total 24
drwxr-xr-x 4 root root 4096 Jan 3 14:10 .
drwxr-xr-x 3 root root 4096 Jan 3 14:10 ..
-rw-r--r-- 1 root root 1017 Jan 3 14:10 CMakeLists.txt
drwxr-xr-x 3 root root 4096 Jan 3 14:10 include
-rw-r--r-- 1 root root 596 Jan 3 14:10 package.xml
drwxr-xr-x 2 root root 4096 Jan 3 14:10 src
###ホスト側から確認
別のターミナルを開いて、ファイルのオーナーを確認します。
myasu@jetsonn:~/gitwork/ros2_ws/src$ ls -la
合計 12
drwxr-xr-x 3 myasu myasu 4096 1月 3 14:10 .
drwxr-xr-x 3 myasu myasu 4096 1月 3 14:09 ..
drwxr-xr-x 4 myasu myasu 4096 1月 3 14:10 pubsubpy_mes
myasu@jetsonn:~/gitwork/ros2_ws/src$ cd pubsubpy_mes/
myasu@jetsonn:~/gitwork/ros2_ws/src/pubsubpy_mes$ ls -la
合計 24
drwxr-xr-x 4 myasu myasu 4096 1月 3 14:10 .
drwxr-xr-x 3 myasu myasu 4096 1月 3 14:10 ..
-rw-r--r-- 1 myasu myasu 1017 1月 3 14:10 CMakeLists.txt
drwxr-xr-x 3 myasu myasu 4096 1月 3 14:10 include
-rw-r--r-- 1 myasu myasu 596 1月 3 14:10 package.xml
drwxr-xr-x 2 myasu myasu 4096 1月 3 14:10 src
ファイルのオーナーが、ホスト側の一般ユーザになっています。
#6.次の課題
- Docker Compose を活用(試し中)
- Dockerで、ROS2 のこの問題が解決できないか・・・
#参考資料
-
https://www.it-swarm.dev/ja/docker/docker%E3%83%9B%E3%82%B9%E3%83%88usb%E3%81%BE%E3%81%9F%E3%81%AF%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%AB%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%81%B8%E3%81%AE%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%82%92%E8%A8%B1%E5%8F%AF%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95%EF%BC%9F/1047354239/ ↩