Help us understand the problem. What is going on with this article?

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

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追記:コンテナの中から、シリアル通信を使う場合の注意点を追記しました。

実行環境

ハード 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

次の課題

  • Docker Compose を活用(試し中)
  • Dockerで、ROS2 のこの問題が解決できないか・・・

参考資料

myasu
片田舎在住の ”日曜” 電子工作員&ソフト屋、時々 山歩きをしています。 https://twitter.com/etcinitd/ https://hotch-potch.hatenadiary.jp/ https://github.com/trihome/ https://speakerdeck.com/myasu/
http://wiki.exatto.org/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした