ラズパイ4デジカメ計画②〜NodejsとGPIOでラズパイの起動・シャットダウンを操作する


※ 当ページには【広告/PR】を含む場合があります。
2024/02/10
2024/02/15
ラズパイ4デジカメ計画①〜格安の(サポートの終了したWiringPI系)液晶タッチパネルを使えるようにする
ラズパイ4デジカメ計画③〜Nodejsでlibcameraを使ったカメラ機能を実装する
蛸壺の中の工作室|NodejsとGPIOでラズパイの起動・シャットダウンを操作する

ラズパイ4Bでなんちゃってデジタルトイカメラに仕立てる話の続きです。

今回もまだカメラの話までは行かずに、Nodejsの動作環境をラズパイに整えていくことに注力していきます。


ラズベリーパイ4B 4GB 技適対応品

ラズパイマガジン 2019年12月号 カメラ&センサー工作入門

ラズパイ専用3.5インチ液晶タッチスクリーン

ラズパイをデジカメ化する上で気にしたい電源管理の注意点

まず、ラズパイを起動・停止させる上で気をつけたいのが、「ラズパイにはサスペンド(休止)状態がない」ということです。

文字通り、電源を入れるか、電源を切るか、二択しかなく、頻繁にON/OFFする使い方をするであろうデジカメにはかなり不利な性質であることは頭においておきましょう。

ラズパイを"スタンバイ"させることができないため、すぐに写真を取りたいタイミングがあっても、電源を付けて、バックグラウンドでアプリが立ち上がるまでに30秒程度は待たなければなりません。

では、
「すぐに写真が撮れるようにタッチパネルだけ切って常時ラズパイを起動させておけば...」という気にもなりますが、バッテリーで電力供給する場合にはこの使い方もかなり厳しいです。

ラズパイ4では、典型的なアプリ動作時には4~5W程度の電力を消費しています。そして何もしていないCPUが低負荷の状態でも、3Wくらい電力消費しているとされています。

問題となるのは、
「シャットダウンした状態でも2W程度消費する」ということです。ゼロでもなく、2mWでもなく、2Wです!

ラズパイの電源を切っているから、大したこと無いだろうとたかをくくっていると、
次の朝ラズパイを起動させたらバッテリー残量がゼロ!という奇妙な体験をするのはコレが原因です。

ともかくラズパイをモバイルバッテリー等で動作させるような用途だと、バッテリーとラズパイ本体の接続を物理的に切断する
「電源スイッチ」は自前で付ける必要があります。

            1. ラズパイには休止機能がないので、
   電源投入からアプリケーション起動までに時間がかかる
2. ラズパイ本体の電源を切っても、
   通電していたら割と電力を消費してしまう
        
こういったことを踏まえて、ラズパイのデジカメ化を以降で進めていきましょう。


ラズベリーパイ4B 4GB 技適対応品

ラズパイマガジン 2019年12月号 カメラ&センサー工作入門

ラズパイ専用3.5インチ液晶タッチスクリーン

ラズパイへNode.js(v16)環境を準備する

巷ではこの手の記事を探すと、おおよそPythonでコードの紹介されている場合がほとんどかと思います。

GPIOを動かすだけであれば、Pythonのほうがたくさん応用事例が紹介されているため、とてもユーザーフレンドリーです。

組み込みでもあえてnodejsを採用する理由は、後々で「TCP/UDPサーバー機能を追加」するまで考えたときに、組込み処理とサーバーとの統合がやりやすいというメリットがあります。

いざNodejsで組込みのプログラムを動かすことを考えたとき、できる限り最新のバージョンで動かしたいのは山々ですが、node18以降ではプログラミング作法がガラリと変わり、可能な限り
ESModuleをベースにしたコーディングが主流になりつつあります。

技術の移り変わりが激しい時期でもあり、ラズパイ向けの組込み用途でNodejsのライブラリ提供されている有志の方のほとんどがESModule対応に追いつけていないため、node18以降に対応したラズパイ向けのライブラリもまだあまり存在していない状況です。

このためラズパイの組込み方面に使われるnodeはレガシーも多く、node18より前の
Commonjsで書かれることがほとんどです。

こういった事情を汲んで、より安定性あるのコーディングが可能な
node16止まりで、カメラのソフト周りを実装することにします。

ラズパイに特定のバージョンのNodejsを導入

以前の記事で、Dockerコンテナ越しにnodejsを動かしてラズパイのLチカを試した内容を紹介していました。

合同会社タコスキングダム|タコツボの中の工作室
【ラズパイでLチカ】Docker Alpineコンテナ上のNode.jsネイティブからラズパイのGPIOを操作する方法

NodejsアプリをDockerコンテナを通じてラズパイのGPIOでLEDをチカチカさせる

今回の用途だと、Dockerを介したデバイスの操作した場合、どうしてもラズパイ本体のON/OFFのたびにDockerエンジン自体のコールドスタートがオーバーヘッド時間で乗ってくる(とはいえ数秒くらいの違いでしょうけれど...)懸念があります。

そこで、デジカメ化に際してはDockerを使わず、ラズパイに直接nodejsを仕込みます。

ラズパイ(Raspberry Pi OS)へのnodejsのインストール方法は現在では様々あります。

昔ながらの外部サイト(
https://deb.nodesource.com)提供のインストールパッケージを利用する方法がやはり簡単かと思います。

            $ curl -s https://deb.nodesource.com/setup_16.x | sudo bash
  This script, located at https://deb.nodesource.com/setup_X, used to
  install Node.js is deprecated now and will eventually be made inactive.

  Please visit the NodeSource distributions Github and follow the
  instructions to migrate your repo.
  https://github.com/nodesource/distributions

  The NodeSource Node.js Linux distributions GitHub repository contains
  information about which versions of Node.js and which Linux distributions
  are supported and how to install it.
  https://github.com/nodesource/distributions


                          SCRIPT DEPRECATION WARNING

================================================================================
▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
================================================================================

TO AVOID THIS WAIT MIGRATE THE SCRIPT
Continuing in 60 seconds (press Ctrl-C to abort) ...
        
...と直近のnodejs事情でv16のインストールスクリプトはサポート切れで利用不能になってしまいました。

そこでちょっと回りくどいですが、
nvm(Node Version Manager)経由でnode16を導入することにします。

            curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
        
インストールはスクリプト一発で簡単ですが、以下のnvmのインストールスクリプトのバージョンはその都度公式のページで確認してください。

スクリプトの処理が完了すると、nvmの起動に必要な環境変数を利用するため、
.bashrcに以下の設定を追記されています。

            #...
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion
        
設定が追加されなかった場合には、手動で追加しておきましょう。

あとはこれを反映して、実行するだけで完了です。

            $ source ~/.bashrc
$ nvm --version
0.39.7
        
では本題のnode16を入れていきます。

            $ nvm list-remote | grep v16
        v16.0.0
        v16.1.0
        v16.2.0
        #...中略
       v16.20.1   (LTS: Gallium)
       v16.20.2   (Latest LTS: Gallium)
        
と利用可能なv16がリストアップされています。

v16の特定のマイナーバージョンは開発環境のものと揃えるか、それほど理由がなければ最新のものを選択しインストールしましょう。

なお、以下のようにメジャーバージョンだけ指定すると、最新のものが自動でインストールされます。

            $ nvm install v16
$ node --version
v16.20.2
$ npm --version
8.19.4
        
なお、パッケージマネージャーもyarnが良い場合には、npmからインストール可能です。

            $ npm install -g yarn
$ yarn --version
1.22.21
        
実際に組込み用のプログラムで、nodeコマンドを使おうと思うと、GPIO等の内部デバイスにアクセスするための権限の設定にもう少し手を加える必要があります。

このことについては後述します。

「sudo node ...」では"command not found"になってしまう問題

組込み向けに
nodeコマンドを使っていくと、ルート権限でなければ扱えないデバイスやアプリケーションにアクセスできない場合があるため、ルートユーザーで入り直すか、sudo nodeコマンドを使うかでこれに対処します。

ですが、単に
sudoからnodeコマンドを呼び出してしまうと、

            $ sudo node --version
sudo: node: command not found
        
となって最初に直面するときには、かなり困惑します。

これは現在のユーザーと、ルートユーザーのシステム変数が異なるため、
nodeコマンドの所在がわからないことを示しています。

            #👇現在のユーザーのパス変数
$ printenv PATH | sed -r 's/:/\n/g'
/home/user/.nvm/versions/node/v16.20.2/bin
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/local/games
/usr/games

#👇ルートユーザーのパス変数
$ sudo printenv PATH | sed -r 's/:/\n/g'
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
        
見てのように、現在のユーザーにはnodeコマンドの所在(/home/user/.nvm/versions/node/v16.20.2/bin)があっても、ルートユーザーからは見えません。

そこで、もっともかんたんな対処法としては
whichコマンドを挟んで、

            $ sudo $(which node) --version
v16.20.2
        
としてあげるとルートユーザーでも上手く処理を行ってくれます。

whichコマンドがいやらしいと感じたら、/usr/bin/の中にシンボリックリンクを張ってあげるとルートユーザーからもnodeコマンドで利用できるようになります。

            $ ln -s $(which node) /usr/bin/
        
ただし、この方法は、nodejsのバージョンやインストール場所が変更になるたびに手動で再度シンボリックリンクを張り直す必要があるので、忘れたころに「nodeバージョンがずれるけどなんで?」となるかもしれません。


ラズベリーパイ4B 4GB 技適対応品

ラズパイマガジン 2019年12月号 カメラ&センサー工作入門

ラズパイ専用3.5インチ液晶タッチスクリーン

ピンレイアウトの確認

ラズパイ用の液晶タッチパネルは結構ピンを占有してしまうので、あまり自由に数を実装できませんが、ボタン操作を実装していきます。

利用している製品にもよりますが、前回に引き続き、液晶タッチパネルは「
ラズパイ専用3.5インチ液晶タッチスクリーン
」を利用しています。

この製品のピンレイアウトは
こちらに描いてあるように、1-26番ピンが既に利用されるため、これらのピンを避けながら物理ボタンを実装しないといけません。

合同会社タコスキングダム|蛸壺の中の工作室

ではどこらへんが空いているかを確認するため
pinoutコマンドを叩きます。

            $ pinout
#...
J8:
   3V3  (1) (2)  5V
 GPIO2  (3) (4)  5V
 GPIO3  (5) (6)  GND
 GPIO4  (7) (8)  GPIO14
   GND  (9) (10) GPIO15
GPIO17 (11) (12) GPIO18
GPIO27 (13) (14) GND
GPIO22 (15) (16) GPIO23
   3V3 (17) (18) GPIO24
GPIO10 (19) (20) GND
 GPIO9 (21) (22) GPIO25
GPIO11 (23) (24) GPIO8
   GND (25) (26) GPIO7
 GPIO0 (27) (28) GPIO1
 GPIO5 (29) (30) GND
 GPIO6 (31) (32) GPIO12
GPIO13 (33) (34) GND
GPIO19 (35) (36) GPIO16
GPIO26 (37) (38) GPIO20
   GND (39) (40) GPIO21
        
ということで空きピンは27-40のGND以外の11ピンが空いている状態です。

GPIO操作系ライブラリを使う上での注意点

前回四苦八苦しながら、ラズパイ用のタッチパネルをセットアップする内容を説明しておりました。

使用したタッチパネルは
「WiringPi」ベースの処理からGPIOを経由してGUIデスクトップ画面を描画しているため、後付で他のGPIO系のライブラリを使うと、内部処理が干渉し、タッチパネルがフリーズして操作を受け付けなくなる不具合があります。

例えば、node18に対応しているGPIO系のライブラリ・
「array-gpio」は、初期化の際にGPIOの設定も全てデフォルトにしてしまうような挙動が入ります。

このため、タッチパネル製品にもよりますが、画面の描画になんらかの不具合が起こってしまうようです。

こういった理由で今回は、WiringPiとは競合せず、かつ、node16までサポートしている
「pigpio」を利用していきます。


ラズベリーパイ4B 4GB 技適対応品

ラズパイマガジン 2019年12月号 カメラ&センサー工作入門

ラズパイ専用3.5インチ液晶タッチスクリーン

GPIOピンからソフトウェアシャットダウン

まずは適当なGPIOピンを使って、子プロセスからshutdownコマンドを走らせることでラズパイを停止させてみましょう。

ここでは、
GPIO20(38番ピン)を使って、ソフトウェアシャットダウン機能を実装します。

pigpiodのインストール

「pigpio」はラズパイの組込み向けに提供されているCで作成されたライブラリ群です。

参考|pigpio library

nodejs版のpigpioは、このインストールしたライブラリを使ってGPIOを利用する方式になっているため、先にベースのプログラムを準備しておく必要があります。

現在のRaspberry Pi OSでは、以下のコマンドで一発導入できます。

            $ sudo apt update
$ sudo apt install pigpio

$ pigpiod -v
79
        
OSのバージョンが古かったり、Debian系以外のOSであれば、ソースコードビルドするとインストール可能です。

            $ wget https://github.com/joan2937/pigpio/archive/master.zip
$ unzip master.zip
$ cd pigpio-master
$ sed -i 's,ldconfig,,' Makefile
$ make
$ sudo make install

$ pigpiod -v
79
        

nodejsプロジェクトの新規作成

まずは適当なフォルダに移動し、以下の
package.jsonを使って、nodejsのプロジェクトを作成してみましょう。

            {
    "name": "raspi-toy-camera",
    "version": "1.0.0",
    "description": "RPi camera app for armhf built in x64 OS",
    "main": "main.js",
    "private": true,
    "dependencies": {
        "pigpio": "^3.3.1"
    }
}
        
この作業フォルダ内で、

            $ yarn install
        
すると、準備はOKです。

では早速pigpioを使ったシャットダウンボタン機能を実装していきます。

以下のように
main.jsを追加しておきます。

            const { execSync } = require('node:child_process');

const Gpio = require('pigpio').Gpio;
//👇GPIO20(38番ピン)をプルアップ入力モードに設定
const button = new Gpio(20, {
    mode: Gpio.INPUT,
    pullUpDown: Gpio.PUD_UP,
});

process.on('SIGINT', () => {
    console.log('Ctrl+Cでプロセス終了します...');
    process.exit(0);
});

const sleep = (_ms) => {
    return new Promise(resolve => {setTimeout(() => resolve(), _ms)});
}

(async () => {
    //👇試しに0.5秒間隔でポーリング
    while(true) {
        console.log("Listening...");
        if (button.digitalRead() === 0) {
            console.log("シャットダウンボタンが押されました!");
            //👇シャットダウンコマンドを呼び出す
            execSync(`/sbin/shutdown -h now 'Turn off by GPIO'`);
        }
        await sleep(500);
    }
})();
        
これで、38番ピンをGNDとショートさせたときに、シャットダウンコマンドが同期的に呼び出され、電源を切ってくれるだけのプログラムが完成します。

なお、実行中のプログラムを手動で止めたいときには、
Ctrlキー+Cキーを同時押しすると中断できます。

プルアップモードでスイッチを配線

ラズパイ内臓のプルアップ抵抗を使った「プルアップ入力モード」を使えば、わざわざ外部に適当な大きさの抵抗を付けなくてもスイッチ入力のテストが簡単に準備できます。

合同会社タコスキングダム|蛸壺の中の工作室

注意が必要なのは、プルアップ設定ですので、スイッチを押していないフロートの状態ではON、スイッチを押したショートの状態ではOFFとなります。

ですので、先程の
pigpioの実装でいうと、

            - スイッチを押さない ... <PIN#>.digitalRead() --> 1
- スイッチを押す ... <PIN#>.digitalRead() --> 0
        
となりますので、入力シグナルの対応は注意してください。

nodeでmain.jsプログラムを起動してみる

ではスイッチとの配線も終わったので、先程の
main.jsを動かしてみましょう。

何も考えずに
nodeコマンドをおもむろに叩いてみます。

            $ node main.js 
2024-02-08 13:50:11 initCheckPermitted: 
+---------------------------------------------------------+
|Sorry, you don't have permission to run this program.    |
|Try running as root, e.g. precede the command with sudo. |
+---------------------------------------------------------+


/home/*********/*********/node_modules/pigpio/pigpio.js:54
    pigpio.gpioInitialise();
           ^

Error: pigpio error -1 in gpioInitialise
    at initializePigpio (/home/*********/*********/node_modules/pigpio/pigpio.js:54:12)
    at new Gpio (/home/*********/*********/node_modules/pigpio/pigpio.js:158:5)
    at Object.<anonymous> (/home/*********/*********/main.js:10:16)
    at Module._compile (node:internal/modules/cjs/loader:1198:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1252:10)
    at Module.load (node:internal/modules/cjs/loader:1076:32)
    at Function.Module._load (node:internal/modules/cjs/loader:911:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:22:47
        
といきなり実行権限の問題で止まります。

ちなみに、ラズパイにはデフォルトユーザーとは別にルートユーザーが存在しています。

特に設定していないと、デフォルトユーザーは自由にデバイスにアクセスできる権限がないので、組込向けのファイルの実行権限には注意が必要です。

ということで、
sudoからローカルインストールしたnodeを実行することにしましょう。

            $ sudo $(which node) main.js 
2024-02-08 13:52:43 initCheckPermitted: 
+---------------------------------------------------------+
|Sorry, you don't have permission to run this program.    |
|Try running as root, e.g. precede the command with sudo. |
+---------------------------------------------------------+


/home/*********/*********/node_modules/pigpio/pigpio.js:54
    pigpio.gpioInitialise();
           ^

Error: pigpio error -1 in gpioInitialise
#...
        
ん?おかしい...。

sudeでルートの実行権限を与えてもまだ実行権限エラーが出ます。

ここでnodeの実行バイナリファイルの所有者情報を詳しく調べます。

            $ ls -la $(which node)
#...
-rwsr-xr-x 1 user user 74174792 Aug  9  2023 node
        
すると、nvm経由で入れたnodeは、rw..."s"というようにルートユーザーのアクセス権が設定されていることが分かります。

この場合、ルートユーザーのファイル実行権が「s」となっているため、他のユーザーからnodeコマンドを実行すると、
sudoしようがしまいが「ファイル所有者の権限で実行される」ことを意味しています。

つまり、sudoしても結局はルート実行権を持たないファイル所有者(ここでは
user)が実行したことにされてしまいます。

こういったアクセス権設定は、内部デバイスなど、ルートユーザー以外からは絶対に実行してほしくないときには便利ですが、ラズパイの組込開発だとしばしば不便なときがあります。

ということで、ルート特権を通常の
755に変更します。

            $ sudo chmod 755 $(which node)
$ ls -la $(which node)
...
-rwxr-xr-- 1 user user 74174792 Aug  9  2023 node
        
これでnodeコマンドをsudoで実行した場合に、ちゃんとルート権限ありで実行されるはずです。

            $ sudo $(which node) main.js
Listening...
Listening...
Listening...
...
#👇スイッチを押すとラズパイがシャットダウン
シャットダウンボタンが押されました!
        


ラズベリーパイ4B 4GB 技適対応品

ラズパイマガジン 2019年12月号 カメラ&センサー工作入門

ラズパイ専用3.5インチ液晶タッチスクリーン

オーバーレイによるシャットダウンピンの書き換え

ラズパイでシャットダウンといえば、デスクトップの電源アイコンをクリックしたり、CUIならshutdownコマンドから電源を切るイメージを持たれている方も多いでしょう。

ラズパイをシャットダウンするだけで良ければ、
DT(デバイスツリー)オーバーレイという機能で、任意のピンを"物理シャットダウンボタン"としてカスタマイズすることもできます。

先程説明した「ソフトウェアシャットダウン」との違いは、常時外部から強制終了できるか否かという点にあります。

ソフトウェアシャットダウンの場合、処理中のプロセスが何らかのエラーで中断してしまったり、暴走して操作を受け付けなくなったりすると、使えなくなってしまいます。

対して、ラズパイ本体に備わっている物理的なシャットダウン機能は、他のプロセスとは独立した機能であるため、異常な事態が起こっても確実に電源を落とすことができます。

まずオーバーレイ機能を変えるには
/boot/config.txtを編集します。

            sudo nano /boot/config.txt
        

            #...中略

#40番ピン(GPIO21)に「シャットダウンボタン」を設定
dtoverlay=gpio-shutdown,gpio_pin=21
        

ラズベリーパイ4B 4GB 技適対応品

ラズパイマガジン 2019年12月号 カメラ&センサー工作入門

ラズパイ専用3.5インチ液晶タッチスクリーン

起動用物理ピン(GPIO3)を使う

ラズパイの起動に関しては、冒頭でも述べたように、物理的な電源投入(電源プラグの抜き差しや電源スイッチの利用)が好ましいのですが、わざわざ起動のたびにケーブルを抜き差しするのが面倒な場合があります。

わざわざ電源の切断と接続を繰り返さなくても、パソコンのような物理的な電源ボタンとなる特別なピンが存在しています。

デフォルトでは
GPIO3(5番ピン)が工場出荷状態で割り当てられています。

このピンは他のピンとは異なり特別であり、GNDピンと短絡することで
「起動」の機能が与えられています。

と言うのは簡単なんですが、ラズパイ用のブレークアウト基板等で、すでにこのGPIO3が使用されている場合、なかなか後付で起動ボタンを追加することが難しい場合があります。

今回の例が分かりやすいですが、以下の写真のようにラズパイ基板の上からも横からも5番ピンに容易に接続できません。

合同会社タコスキングダム|蛸壺の中の工作室

ということで、少しトリッキーな感じになりますが、裏側のすでにハンダされてるところを狙って、ワイヤーを一本引き出してあげましょう。

合同会社タコスキングダム|蛸壺の中の工作室

既にハンダされているピンにそのまま上からハンダすると、なかなか思うように付かないので、あらかじめフラックスを塗布してからハンダすると良いでしょう。

この方法だと、接続がどうしても弱くなって、何気ない衝撃でハンダ箇所が外れてしまうことも考えられるので、より強固にしたい場合には、グルーガンなどで被覆すると良いかもしれません。


ラズベリーパイ4B 4GB 技適対応品

ラズパイマガジン 2019年12月号 カメラ&センサー工作入門

ラズパイ専用3.5インチ液晶タッチスクリーン

まとめ

今回は、デジカメ化とは直接的には関係しませんが、持ち運び可能なラズパイガジェット化にとってはとても重要なことなので、先んじて本体電源のピン操作をまとめてみました。

次回からは、デジカメ化のコアな内容である、カメラモジュールの実装に関して解説してきます。