【ラズパイでもDocker!】Docker Alpineコンテナ上のNode.jsネイティブアプリからラズパイのGPIOを操作する方法
※ 当ページには【広告/PR】を含む場合があります。
2020/05/08
過日に記事にした
今回は
Nodejs
まずは
Nodejs
npmパッケージの利用① 〜 onoff編
まずは、一番簡単にGPIOを操作できる
以降では前提として、Raspbian Busterのラズパイ3B+にDockerがインストールされていることとします。 ただしDockerさえ導入できれば、どのOSでも同様の手順でLチカ出来ると思います。
今回利用するdockerイメージは既に
nodejs
alpine
nodejs13+alpine3.11
arm32v7/node:alpine
前回の記事で取り上げたラズパイで動作する
alpine
nodejs
$ docker pull arm32v7/node:alpine
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
arm32v7/node alpine 30bb03f6ec2e 6 months ago 96.6MB
armhf/alpine latest 15ed6d4bf10d 2 years ago 3.6MB
ちなみに、ラズパイ3B+に搭載されているCPUアーキテクチャは
Broadcom BCM2837
Cortex-A53
ARMv8
ARMv8
ARMv7
不足のapkパッケージ追加
ではまず
arm32v7/node:alpine
onoff
-v /sys:/sys
$ docker run -it \
--rm \
-v /sys:/sys \
arm32v7/node:alpine /bin/sh
ここから
onoff
$ apk add --update python make g++
これが、内部で
node-gyp rebuild
npmプロジェクトの作成
次にnpmの初期化と、エントリーポイントとなる
index.js
ここでは一例として
/usr/local/app
$ mkdir /usr/local/app && cd /usr/local/app
#👇新規のpackage.jsonを発行
$ npm init -y
Wrote to /usr/local/app/package.json:
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
#👇index.jsがアプリのエントリーポイント
$ touch index.js
$ ls
index.js package.json
それでは
onoff
$ npm i onoff
> epoll@4.0.0 install /usr/local/app/node_modules/epoll
> node-gyp rebuild
make: Entering directory '/usr/local/app/node_modules/epoll/build'
CXX(target) Release/obj.target/epoll/app/epoll.o
SOLINK_MODULE(target) Release/obj.target/epoll.node
COPY Release/epoll.node
make: Leaving directory '/usr/local/app/node_modules/epoll/build'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN src@1.0.0 No description
npm WARN src@1.0.0 No repository field.
+ onoff@6.0.0
added 6 packages from 12 contributors and audited 6 packages in 26.364s
found 0 vulnerabilities
これで
onoff
Lチカ
以下を
index.js
const { Gpio } = require('onoff');
// GPIO2を出力設定で利用
const ledOut = new Gpio('2', 'out');
// LEDの状態(false=消灯)
let isLedOn = false;
// 一定時間間隔でループ処理
setInterval(() => {
ledOut.writeSync( isLedOn ? 0 : 1 ); // 1の書き込みで点灯・0で消灯
isLedOn = !isLedOn; // 状態を反転
}, 500); // 0.5秒
これで準備はOKです。 では以下のコマンドでLチカさせてみましょう。
$ node index.js
#...ずっとチカチカ
なお終了させるときは
cntl + c
余談 ~ --deviceオプションだけだとonoffは動かない
dockerの
--device /dev/gpiomem
onoff
--device
$ docker run -it \
--rm \
--device /dev/gpiomem \
arm32v7/node:alpine /bin/sh
それで、上記の
index.js
$ node index.js
internal/fs/utils.js:220
throw err;
^
Error: EROFS: read-only file system, open '/sys/class/gpio/export'
at Object.openSync (fs.js:440:3)
at Object.writeFileSync (fs.js:1281:35)
at exportGpio (/usr/local/app/node_modules/onoff/onoff.js:18:8)
at new Gpio (/usr/local/app/node_modules/onoff/onoff.js:172:36)
at Object.<anonymous> (/usr/local/app/index.js:4:16)
at Module._compile (internal/modules/cjs/loader.js:1063:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1103:10)
at Module.load (internal/modules/cjs/loader.js:914:32)
at Function.Module._load (internal/modules/cjs/loader.js:822:14)
at Function.Module.runMain (internal/modules/cjs/loader.js:1143:12) {
errno: -30,
syscall: 'open',
code: 'EROFS',
path: '/sys/class/gpio/export'
}
とエラーが発生して動きません。
そもそも
onoff
/sys/class/gpio
--device
onoff
onoff
npmパッケージの利用② 〜 pigpio編
ラズパイのGPIOをnodejsで操作するためのパッケージは非常に多くnpm等で公開されていますが、逐一取り上げていたらキリがないので、個人的にも気に入っている
pigpio
とりあえず先のほどのようにベースイメージから、色々とパッケージを手動でインストールしていくのは骨折りな作業ですので、 今回はインタラクティブモードで開発するバージョンのDockerfileを用意します。
FROM arm32v7/node:alpine
RUN apk update && \
apk upgrade && \
apk add --no-cache python make g++ alpine-sdk unzip bash
RUN wget https://github.com/joan2937/pigpio/archive/master.zip && \
unzip master.zip && \
cd pigpio-master && \
sed -i 's,ldconfig,,' Makefile && \
make && \
make install
WORKDIR /usr/local/app
COPY package.json /usr/local/app
COPY index.js /usr/local/app
RUN npm install
これをオリジナルDockerイメージとしてローカルビルドさせます。 ここでは自分専用として
taconocat/pigpio
$ docker build . -t taconocat/pigpio
これをコンテナ起動させてインタラクティブモードで開発を進めていきます。
$ docker run -it \
--rm \
--privileged \
taconocat/pigpio /bin/bash
とりあえず苦肉の策として
--privileged
index.js
package.json
index.js(エントリーポイント)
const Gpio = require('pigpio').Gpio;
const led = new Gpio(2, {mode: Gpio.OUTPUT});
let isLedOn = false;
// 一定時間間隔でループ処理
setInterval(() => {
if (isLedOn) {
led.digitalWrite(1);
} else {
// led.off();
led.digitalWrite(0);
}
isLedOn = !isLedOn; // 状態を反転
}, 1000); // 0.5秒
package.json
{
"name": "pigpio-master",
"version": "1.0.0",
"description": "pigpio is a C library for the Raspberry which allows control of the General Purpose Input Outputs (GPIO).",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"pigpio": "^3.2.1"
}
}
コンテナ起動後に、インタラクティブモード越しに
node index.js
余談 〜 特権コンテナにしないと動かない?
今回は深く追求しませんが、
--device
--device
--cap-add
--device
$ docker run -it \
--rm \
--cap-add SYS_ADMIN \
--device /dev/gpiomem \
taconocat/pigpio /bin/bash
インタラクティブモードに入り
node
$ node index.js
2020-05-07 16:13:07 initCheckPermitted:
+---------------------------------------------------------+
|Sorry, you don't have permission to run this program. |
|Try running as root, e.g. precede the command with sudo. |
+---------------------------------------------------------+
/usr/local/app/pigpio-master/node_modules/pigpio/pigpio.js:29
pigpio.gpioInitialise();
^
Error: pigpio error -1 in gpioInitialise
at initializePigpio (/usr/local/app/pigpio-master/node_modules/pigpio/pigpio.js:29:12)
at new Gpio (/usr/local/app/pigpio-master/node_modules/pigpio/pigpio.js:133:5)
at Object.<anonymous> (/usr/local/app/pigpio-master/index.js:3:13)
at Module._compile (internal/modules/cjs/loader.js:778:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Function.Module.runMain (internal/modules/cjs/loader.js:831:12)
at startup (internal/bootstrap/node.js:283:19)
という感じで、
node
今回はLチカさせたいだけなので、色々と細悩むよりは
--previliged
特権コンテナ
ただし本番用の製品などで
--previliged
本番用のプロセス実行コンテナ
それでは最後に、先程のインタラクティブモード用の開発コンテナから、本番用の常駐プロセスで走らせて使用する本番用のコンテナに仕立て直します。
開発用イメージ内のアプリケーションビルドに利用したパッケージが無駄に残っているので、ビルド後に用済みになっているパッケージは削ります。
FROM arm32v7/node:alpine
RUN apk update && \
apk upgrade && \
apk add --no-cache --virtual build-deps python make g++ alpine-sdk unzip
RUN cd /tmp && \
wget https://github.com/joan2937/pigpio/archive/master.zip && \
unzip -qq master.zip && \
cd pigpio-master && \
sed -i 's,ldconfig,,' Makefile && \
make && \
make install && \
rm -rf /tmp/*
WORKDIR /usr/local/app
COPY package.json /usr/local/app
COPY index.js /usr/local/app
RUN npm install && \
apk del build-deps
CMD ["node", "index.js"]
この実行用のコンテナを開始+常駐化するため以下のコマンドで立ち上げます。
#デタッチモードで起動
$ docker run -d --privileged taconocat/pigpio
#起動デーモンに登録し常駐化
$ docker update --restart=always taconocat/pigpio
これでLEDをチカチカする処理がバックグラウンドで永続化できると思います。
まとめ
今回はnodejsによってdockerコンテナ側からでもラズパイのホスト側のデバイスをc++ライブラリを介して直接レジスターアクセスで操作できるまでをダイジェストに解説していきました。 これを活用することで、nodejsベースのアプリケーションでも、オーバーヘッド時間の短いデバイスへの高速処理が可能となりました。
今後はQEMUなどのエミュレータと組み合わせながら、実践的な開発の応用事例などをブログ記事で発信できればいいなと思っております。