chakokuのブログ(rev4)

テック・コミック・DTM・・・ごくまれにチャリ

Node-REDからAmazon Rekognitionを呼び出してみる→動くには動いた

課題:Node-REDからAmazon Rekognitionを呼び出すためのオリジナルNodeを作る
取り組み:サンプルNode(lowercase)をベースに、先日動かしたRekognitionサンプルコードをはめ込んで動かしてみる
結論:動くには動いた。
問題点:自分のRPiと生徒用のRPiはOSの設定がかなり違う。このまま開発を続けても再移植が大変になるので、生徒用RPiで最終の仕上げを進める。

詳細:
まずはサンプルをそのまま動かすのだが、、、自分が動かしているNode-REDはDocker内なので、ストレージを外出しにしないと永続化できない。。
Node-REDの指示通りに以下で動かしていた

docker run -it -p 1880:1880 --name mynodered nodered/node-red

どのディレクトリが使われているのか、先日作成されたコンテナに入って確認
node-redのプログラム本体は、/usr/src/node-red配下に置かれて、

~ $ pwd
/usr/src/node-red
~ $ ls
entrypoint.sh  node_modules   package.json

ユーザデータは、/data配下に置かれるようであった。

/data $ pwd
/data
/data $ ls -F
flows.json         lib/               package-lock.json  settings.js
flows_cred.json    node_modules/      package.json

だから、無難に考えると、/usr/src/node-redと、/dataの両方を外だしするのが確実かと。
上記コマンドに対して、node-redのhomeがローカルPCのディレクトリになるよう以下で起動する

#------------------------------
#!/bin/sh

LOCAL_DATA=/home/<user_name>/node-red/container/fs/data/
#LOCAL_BIN=/home/<user_name>/node-red/container/fs/node-red/

sudo docker run -it -p 1880:1880 --name nodered-v3 \
    --mount type=bind,source=${LOCAL_DATA},destination=/data \
    nodered/node-red

#------------------------------
#    --mount type=bind,source=${LOCAL_BIN},destination=/usr/src/node-red \

当初、/usr/src/node-redも外だしする予定だったが、該当ディレクトリに置かれるはずの./entrypoint.sh が読めない(配置できない?)というエラーが出てコンテナが起動できなかった。dockerのコンテナビルドのシーケンスがちゃんと理解できておらず、(本来は深堀すべきだが)一旦ユーザ領域だけ外だしするように変更

Dockerコンテナ内に拡張ノードを構築するためのワークディレクトリを作成、適当に作ると消えてしまうので、永続化されている、/data配下に作成する(/data/myextension)
サンプルを上記ワークディレクトリ(/data/myextension)配置、ワークディレクトリ内(/data/myextension)でnpm initを実行してpackage.jsonを生成して、所定のJSONコードを追加。この結果、ワークディレクトリ内は以下の状態

/data $ pwd
/data

/data $ ls -F myextension/
lower-case.html  lower-case.js    package.json

/data $ cat myextension/package.json
{
  "name": "node-red-contrib-example-lower-case",
  "version": "1.0.0",
  "description": "",
  "main": "lower-case.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
   "node-red" : {
        "nodes": {
            "lower-case": "lower-case.js"
        }
    }
}

Node-REDのユーザ用ディレクトリ(/data)において、npm installコマンドを実行、
/data/node_modules配下に、追加したノード(node-red-contrib-example-lower-case)のリンクファイルができる。

/data $ pwd
/data

/data $ npm install /data/myextension

added 1 package, and audited 3 packages in 848ms

/data $ ls
flows.json         lib                node_modules       package.json
flows_cred.json    myextension        package-lock.json  settings.js

/data $ ls -l node_modules/
total 0
lrwxrwxrwx    1 node-red node-red        14 Aug 17 01:29 node-red-contrib-example-lower-case -> ../myextension

絶対パスでinstallコマンドを実行したはずが、相対パスでリンクが作られている・・)
Node-RED起動時に読み込まれるということなので、、コンテナを一旦止めて再起動、すると以下の通り読み込まれている。

実際に大文字小文字変換ノードを動かしてみる。小文字変換は正しく動いている

ということで、、サンプルノードは作ることができたので、これをベースにしながらRekognitionノードを作ってみる。
昨日はUbuntu環境で作業したが、今回はコンテナ内で作業する
npmコマンドを用いて、Node.js用にAWS SDK をインストールする

npm install aws-sdk

追加モジュールは、/usr/src/node-red/node_modules 配下に置かれる(ここは永続化されていないので、消える可能性あり*1 )
配布されているコンテナイメージにパッケージを追加することはポリシーから外れていると認識しているが*2、、まぁトライアルなので
コンテナ内で自分は誰かを確認、 uidはnode-redで、 HOMEは/usr/src/node-red であった

/data/work $ id
uid=1000(node-red) gid=1000(node-red) groups=1000(node-red)
/data/work $ echo $HOME
/usr/src/node-red

オプション指定で変えられるかもですが、、一旦 AWS API接続用credentialsをコンテナ内の以下に配置

/usr/src/node-red/.aws/credentials

一応動いた。しかも早い。なぜ早いのだろうか。。ちょっと理由が分からず

/data/work $ ./rekog.js
calling method
fin
(node:1623) NOTE: We are formalizing our plans to enter AWS SDK for JavaScript (v2) into maintenance mode in 2023.

Please migrate your code to use AWS SDK for JavaScript (v3).
For more information, check the migration guide at https://a.co/7PzMCcy
(Use `node --trace-warnings ...` to show where the warning was created)
Detected faces for: face001.jpg
The detected face is between 52 and 60 years old

余計なトラップに落ちずにまずは動かしたいので、lower-caseサンプルに対して、Rekognitionを呼び出す様に改変
Node名はlowercaseのままだが、やっていることは、Amazon Rekognitionを呼び出して、結果を非同期で返す実装とした。

module.exports = (RED) => {
    function LowerCaseNode(config) {
        RED.nodes.createNode(this,config);
        var node = this;
        node.on('input', function(msg) {
            lower(node, msg);  // .toLowerCase() + "how??" ;
        });
    }
    RED.nodes.registerType("lower-case",LowerCaseNode);
}

function lower(node, msg) {

   const AWS = require('aws-sdk');   // should be let ??

   const REGION = 'us-east-1';
   const BUCKET =  <your bucket name> ;     // e.g  'rekognition-bucket'
   const PHOTO =  <your photo file name>;  //  e.g.  'face001.jpg'
   const PROFILENAME = 'default';

   const credentials = new AWS.SharedIniFileCredentials({profile: PROFILENAME});
   AWS.config.credentials = credentials;
   AWS.config.update({region:REGION});

   const client = new AWS.Rekognition();
   const params = {
       Image: { S3Object: { Bucket: BUCKET, Name: PHOTO }},
       Attributes: ['ALL']
   }
   console.log("i ll  call  AWS Recognition");

   client.detectFaces(params, (err, response) => {
        if (err) {
           console.log(err, err.stack); // an error occurred
           msg.payload = "Error at AWS Rekognition"; 
           node.send(msg);
        } else {
           console.log(`Detected faces for: ${PHOTO}`);
           msg.payload = response.FaceDetails[0];
           node.send(msg);
       }
   });
   return (true);            // no need return value
}

コストのかかるインスタンス生成を毎回実行するのが正しいやり方なのか?ちと分からないが、グローバル環境に影響を与えないためには、関数内で都度インスタンスを作るのが無難かと。。*3
Node名は不適切なままだが、一応動いた。実行時間も気にするほど遅くない。

今後の取り組み
カメラ制御、Amazon Rekognitionの呼び出しと部品はだいたいそろったので、これらが連携して動くように細部を調整する。特に、仮運用のdocker上のNode-REDで開発を続けると、最終環境に持っていくのが大変になる。試作はこれぐらいにして、教材相当のGUI付きRPi上で構築する方が良いと判断

■おまけ
これまで自分はPython/MicroPythonだけを使ってきた。が、、諸般の事情によりJSも(多少は)分かっている必要があり勉強を始めた。JSはClientSide/ServerSideの各種サービスで幅広く活用されており、今回のようなNode-REDもJS(Node.js)で実装されている。JSを使いこなせる必要性を改めて感じた。

■参考URL
はじめてのノード開発 : Node-RED日本ユーザ会
Node.jsからAmazon Rekognitionを呼び出してみる - chakokuのブログ(rev4)

*1:コンテナ本体を消さなければ温存されるが・・

*2:ポリシー的にはdockerファイルを作り直してイメージを再ビルドすべきかと

*3:JSではインスタンスを作る概念はない??