LoginSignup
43
36

More than 3 years have passed since last update.

DockerコンテナからRaspberryPiのGPIO・I2C・シリアル通信を使う

Last updated at Posted at 2020-02-18

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

Dockerfile
# 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)

Dockerfile
# 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
/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

編集前

/etc/subuid
dockremap:100000:65536

編集後

/etc/subuid
dockremap:1000:65536

ホストOS側の自分のUIDが1000の筈ですので、その値に修正します。

続いてもう一つのファイル。

$ sudo nano /etc/subgid

編集前

/etc/subgid
dockremap:100000:65536

編集後

/etc/subgid
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 のこの問題が解決できないか・・・

参考資料

43
36
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
43
36