まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:nodered

IBM Bluemix からも提供しているビジュアルフローエディタ Node-RED の最新バージョン v0.16 がリリースされました(2017/Jan/17 時点での最新版は v0.16.1):
https://libraries.io/npm/node-red/


新機能などの情報についてはこちらの公式ブログエントリや、
http://nodered.org/blog/2017/01/11/version-0-16-released

こちらのリリースノートを参照ください:
https://github.com/node-red/node-red/releases/tag/0.16.0


さて、上記ブログエントリにも書かれているのですが、この v0.16 より Node.js の v0.10 および v0.12 がサポート外になりました。Node.js も v4 以上のものを用意する必要があります。

これで問題になるのが CentOS/RHEL 6.x を使っている場合です。現在、CentOS/RHEL 6.x で(epel リポジトリなどを使って)普通に Node.js を導入すると v0.10.48 が導入されます:
# node -v
v0.10.48

IBM からも IBM SDK for Node.js として IBM 版の Node.js がリリースされていますが、こちらも CentOS/RHEL 6.x は v0.12 までしか用意されておらず、Node.js v4 以上を使おうとすると CentOS/RHEL であれば 7.x を使う必要があります:
https://developer.ibm.com/node/sdk/

2017011702


ちなみに Node.js v0.10.x が導入された状況下で深く考えずに npm を使って Node-RED を導入すると、最新バージョン(今であれば v0.16.1)が導入されてしまいます。その結果起動時にエラーが発生し、ユーザー画面にもエラーメッセージが表示されてしまいます:
# npm install node-red -g  (このコマンドは成功する)

# node-red
  :
  :
Welcome to Node-RED
===================

17 Jan 10:10:33 - [info] Node-RED version: v0.16.1
17 Jan 10:10:33 - [info] Node.js  version: v0.10.48
17 Jan 10:10:33 - [error] *****************************************************************
17 Jan 10:10:33 - [error] * Unsupported version of Node.js. Requires: >=4 Found: v0.10.48 *
17 Jan 10:10:33 - [error] *****************************************************************
17 Jan 10:10:33 - [info] Linux 2.6.32-642.11.1.el6.x86_64 x64 LE
17 Jan 10:10:33 - [info] Loading palette nodes

/usr/lib/node_modules/node-red/node_modules/mqtt/lib/connect/index.js:100
    const isSecure = ['mqtts', 'wss'].indexOf(opts.protocol) !== -1
    ^^^^^
17 Jan 10:10:35 - [warn] ------------------------------------------------------
17 Jan 10:10:35 - [warn] [rpi-gpio] Info : Ignoring Raspberry Pi specific node
17 Jan 10:10:35 - [warn] [mqtt] SyntaxError: Use of const in strict mode.
17 Jan 10:10:35 - [warn] ------------------------------------------------------
17 Jan 10:10:35 - [info] Settings file  : /root/.node-red/settings.js
17 Jan 10:10:35 - [info] User directory : /root/.node-red
17 Jan 10:10:35 - [info] Flows file     : /root/.node-red/flows_xxxxxxxx.xxxxx.xxx.json
17 Jan 10:10:35 - [info] Creating new flow file
17 Jan 10:10:35 - [info] Server now running at http://127.0.0.1:1880/
17 Jan 10:10:35 - [info] Starting flows
17 Jan 10:10:35 - [info] Started flows

↑起動が成功したように見えるが、バージョン非互換のエラーが発生している

2017011701


では改めて CentOS/RHEL 6.x のバージョンアップをせずにどうやって Node-RED を導入すればよいでしょうか? Node-RED の最新版を動かすために工夫する、というのも1つの方法ですが、Node-RED が最新版でなくてもよい場合であれば Node-RED v0.14.x を使う、という方法もあります。このバージョンであれば CentOS/RHEL 6.x でも動作します。

その場合の手順を紹介します。まず上記のコマンドを実行してしまって Node-RED v0.16.x を導入してしまった場合はアンインストールします:
# npm uninstall node-red -g

リリースサイトを見ると、v0.14.x の最新バージョンは v0.14.6 のようです(2017/Jan/17 時点)。というわけで、改めてこのバージョンを指定して npm でインストールします:
# npm install node-red@0.14.6 -g

こうしてインストールした Node-RED を起動すればエラーは発生しません:
# node-red
  :
  :
Welcome to Node-RED
===================

17 Jan 10:15:55 - [info] Node-RED version: v0.14.6
17 Jan 10:15:55 - [info] Node.js  version: v0.10.48
17 Jan 10:15:55 - [info] Linux 2.6.32-642.11.1.el6.x86_64 x64 LE
17 Jan 10:15:55 - [info] Loading palette nodes
17 Jan 10:15:58 - [warn] ------------------------------------------------------
17 Jan 10:15:58 - [warn] [rpi-gpio] Info : Ignoring Raspberry Pi specific node
17 Jan 10:15:58 - [warn] ------------------------------------------------------
17 Jan 10:15:58 - [warn] Missing node modules:
17 Jan 10:15:58 - [warn]  - node-red: yaml
17 Jan 10:15:58 - [info] Removing modules from config
17 Jan 10:15:58 - [info] Settings file  : /root/.node-red/settings.js
17 Jan 10:15:58 - [info] User directory : /root/.node-red
17 Jan 10:15:58 - [info] Flows file     : /root/.node-red/flows_xxxxxxxx.xxxxx.xxx.json
17 Jan 10:15:58 - [info] Creating new flow file
17 Jan 10:15:58 - [info] Starting flows
17 Jan 10:15:58 - [info] Started flows
17 Jan 10:15:58 - [info] Server now running at http://127.0.0.1:1880/

↑起動成功

というわけで、現時点で比較的簡単に CentOS/RHEL 6.x で Node-RED を動かす場合はこの方法になるのかな、と思ってます:
2017011703


なお、CentOS/RHEL 6.x に Node.js v4.x を導入してから Node-RED v0.16.x を入れる、という方法もあります。CentOS/RHEL 6.x に Node.js v4.x を入れる方法についてはこちらに詳しく書かれていました:
CentOS 6.xにLTS(4.3.0)のnode.jsをインストールする

以前に CentOS 環境に StrongLoop LoopBack を導入する手順を紹介しました:

上記の手順ではまず Node.js を導入し、その Node.js 上に StrongLoop LoopBack を導入して OpenAPI 環境を構築していました。リンク先でも紹介されているように、この方法では LoopBack(Node.js) の起動をコマンドラインで行う必要がありました。つまりシステムが起動した後に明示的に LoopBack(Node.js) サーバーをアプリケーションとして起動する、という方法でした:
# cd myapp
# node .

今回の目的はこれをデーモン化することです。

LoopBack に限った話ではないのですが、Node.js アプリケーションをデーモン化する場合には forever というツールを使うことが多いです。forever は Node.js スクリプトの死活監視ツールで、 npm を使ってインストールします:
# npm install forever -g

forever で Node.js アプリをデーモン化して起動する場合は、forever コマンドの start オプションとして実行する JavaScript ファイルを指定します。LoopBack の場合はアプリケーションディレクトリ(上記例の場合は myapp)に package.json というファイルがあり、その中に以下のような記述があります:
{
  "name": "myapp",
  "version": "1.0.0",
  "main": "server/server.js",
  "scripts": {
    "start": "node .",
      :
      :

つまり LoopBack の場合は server/server.js が起動時に実行されていることがわかります。したがって forever を使って LoopBack アプリケーションを起動する場合はアプリケーションディレクトリからこのように server/server.js ファイルを指定すればよい、ということになります:
# cd myapp
# forever start server/server.js

これで LoopBack が実行されます。ウェブブラウザで http://(LoopBack アプリサーバー):3000/explorer にアクセスすると、Open API の API Reference にアクセスできることが確認できます:
2016061601


なお forever は start 以外にも以下の様なコマンドが用意されています:
コマンド目的
forever start (Node.js JavaScript ファイル)指定したアプリケーションの起動
forever list実行中のスクリプトの表示
forever stop (Node.js JavaScript ファイル)指定したアプリケーションの停止
forever stopall起動中の全アプリケーションの停止
forever restart (Node.js JavaScript ファイル)指定したアプリケーションの再起動
forever restartall起動中の全アプリケーションの再起動







 

※(注)最初にお断りしておきます。マジメぶって書いてますが馬鹿エントリです。


全てのきっかけは最近たまにみかけるこういった記事でした:

人工知能やコグニティブエンジンと呼ばれる技術の発達により、これまで人間の脳でないと判断できなかったようなことをコンピュータができるようになり、人間の仕事がより低コストな人工知能やそれらを搭載したロボットによって奪われてしまう時代がくる、という啓発記事です。

個人的にはそこまでそんな時代が身近に迫ってきているとは思っていません。ただし、その一方で企業間の競争が働いたこともあり、ここ数年における人工知能分野の発展はすさまじいものがあります。静止画像や個人の機械認識率はぐんと上がり、各社が API を公開している背景も手伝って、人工知能に触れる機会がより身近な世界になりつつあるのも事実だと思っています。以下は個人的見解ですが、クリエイティブな仕事(例えば小説を書く、など)を学習させるのはまだ難しいにせよ、脳を使わない単純作業や、ルーチン化された業務などは意外と早い段階で人間の効率を追い抜く日がやってくるかもしれない(そうなると作業コストで勝てるわけがないので、本当に仕事を失う日がやってくるかもしれない)、と思っています。

例えばお客様とお話ししていて、ただ頷いて聞いているだけ。お客様が話し終わったらすかさず相槌を打つ、そんなフローチャートのような業務では近い将来に職を奪われてしまうかもしれないのです!
(注 実在する誰かのことではありません)
2015022305


さて話は変わって、先日秋葉原でこのような部品を買ってしまいました:

2015022300



この SEN02281P はいわゆる「音センサー」です。画像左上にある大きな丸い部分がマイクになっていて、ここで音を拾って、その情報を電気回路を通じて外部に知らせることができる、というものです。買った後で知ったのですが、Arduino に接続したり、Raspberry PiGrovePi という拡張モジュールを取り付けて簡単に使う方法がネットなどで紹介されていました。

・・・ん、もしかすると、この SEN02281P と(例えばラズベリーパイとかの)演算機能を使えば、上記のような簡単なデータフローが実現できてしまうんじゃないだろうか? つまり「誰かが喋っている時は頷き、喋り終わったら相槌を入れる、という仕組みは、このセンサーとアルゴリズムを実装するプログラムだけで実現できてしまうんじゃないだろうか?」ということに気付いてしまったのです! というわけで、よく調べずにとりあえず買ってしまいました。


自分は「ラズベリーパイならメジャーだからまあ繋がるだろう、その方法もネットで見つかるだろう」とタカを括っていたのですが、これが意外と苦戦しました。結論からいうと GrovePi を使わない方法(ラズベリーパイの GPIO に直接繋げる方法)を見つけることができませんでした。えーマジで!?自分で調べるしかないの??電子回路は苦手なんだよなあ。。まあ挑戦してみました。以下、やってみたことのおさらいの意味で書いてます。当方こっち方面はド素人なので、間違いを見つけたり、こうするともっといいよ、という方法があればウェルカム、というか教えてください。

・・・改めてパーツを眺めてみました。接続端子はこの↓画面上部の白い四角の中に生えた4つの突起部分です:
Loudness_101020063_01

これを裏返すとこんな感じ(上図とは左右が逆になった状態):
2015022301


拡大するとこんな感じ。ちょっと見難いのですが、この画面の左から順に(反対から見た場合は右から順に) SIG / NC / VCC / GND と書かれています:
2015022302


GND はアース(Ground)、VCC は電圧、これは(他の部品とほぼ共通なので)分かる。上記の商品ページを見ると、このパーツの動作電圧は 5V(3.5~10V) と書かれているので、とりあえず 5V の電圧ピンにジャンパケーブルを繋いであればいいかな。そして SIG はシグナル、つまりここを GPIO27 とかに繋げて音の信号を受け取るんだろうな・・・ で、NC ?なんだこれ、見たことないぞ・・・

で、ここだけ調べて分かったのは NC = Not Connected 、つまり「どことも繋がない」という端子らしい。そうなんだ。。じゃ、なんで存在してるんだろ?? うーん・・・まあ、いいやw

というわけで、ジャンパケーブルを使ってこんな感じで自分のラズベリーパイの GPIO に接続しました:
2015022303

これで 5V の電源を供給し、アースも備え、SEN02281P が感知した音を取り込む仕組みが動くはずです。実際の写真はこんな感じです(メス-メスのジャンパーケーブルがあればもっと綺麗に接続できたのに・・・):
2016022400


そして、ラズベリーパイ側にはこのような Python プログラムを導入しました:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# SEN02281P ----- RaspberryPi GPIO
# =SIG ---------- 13
# =NC
# =VCC ---------- 2
# =GND ---------- 20

import paho.mqtt.client as mqtt
import time
import RPi.GPIO as GPIO

def on_connect(client,userdata,flags,rc):
	print( "Connection with result code " + str(rc) )
	client.subscribe( "sen02281p" )

def on_message(client,userdata,msg):
	print( msg.topic + " " + str(msg.payload) )

def reading(sensor):
	sum = -1 
	if sensor == 0:
		sum = 0
		for i in range(0,20):
			time.sleep(0.1)
			a = GPIO.input(SIG)
			sum += a
	else:
		print "Incorrect function."

	return sum

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BOARD)
SIG = 13
GPIO.setup(SIG,GPIO.IN)

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message

client.connect( "iot.eclipse.org", 1883 )
while client.loop() == 0:
	msg = reading(0);
	client.publish( "sen02281p", msg, 0, True )
	pass

GPIO.cleanup()
※事前に pip install paho-mqtt をして、Paho の Python ライブラリを導入済みです。

ちなみに上記ソースファイル(sen02281p.py)はこちらからダウンロードできます:
https://raw.githubusercontent.com/dotnsf/sen02281p/master/sen02281p.py


GPIO の(SEN02281P の SIG 端子とつながっている)13番ピンからのインプット情報を 0.1 秒ごとに20回(つまり2秒間)取得し、その20回中音が確認できた回数を MQTT ブローカー(iot.eclipse.org)に投げる、というものです。つまり MQTT ブローカーに対しては2秒おきにどれくらい音が識別できたかを 0 から 20 の整数値でパブリッシュする、というものです。トピックは上記例では "sen02281p" と指定していますが、皆さんがもしこのソースを使う場合は少し変えていただけると嬉しいです。

次に Bluemix 上の NodeRED 環境を使って、このパブリッシュされたメッセージを取り出す仕組みを用意します。MQTT インプットノードを用意し、ホスト名に上記の MQTT ブローカーホスト(iot.eclipse.org:1883)、トピックに "sen02281p" を指定しています。このノードからは2秒に1回、ラズベリーパイの接続された SEN02281P のマイクから拾った音の頻度が渡されてくる、という仕組みとなります。また、その取り出した結果を /ws/unzk_sensor というパスの WebSocket に出力しています:
2015022304


後はこの /ws/unzk_sensor からリアルタイムにデータを取り出して動く WebSocket アプリケーションを用意してあげればマイクで拾った音の頻度をリアルタイムに可視化するようなアプリケーションを作ることができる、ということになります。そのサンプルアプリ(やその中で使う画像)も合わせてこちらで公開しておきます:
https://github.com/dotnsf/sen02281p


このアプリを上記の NodeRED 環境にデプロイしてアプリケーション(hoho.html)を開くと、このような画面になります:
2016022401


ひたすら「頷いている」画面になっています。何もデータが送られていないとただ頷いているだけですが、一応この画面が出ればデプロイには成功していることになります。

ではラズベリーパイ側のアプリも起動します。ネットに接続されたラズベリーパイ上で先程の MQTT パブリッシャーアプリを実行します:
# python sen02281p.py

そして先程の頷き画面をリロードすると・・・ ラズベリーパイに接続されたマイクが音を拾っている間は頷き、音が途切れたと判断した時に「ほほー」と相槌を売ってくれるようになります!
2016022402


この「音を拾っているか」「音が途切れたか」の判断がまだ少し甘いところがあるかもしれませんが、一応それっぽく動いていることが確認できました。これで忙しい営業さんに変わってお客様のお話しを上手に引き出してくれるロボットができました(笑)。


実際に動いている動画を Ustream に上げておきます:

Live streaming video by Ustream




IBM Bluemix からも提供されている NodeRED データフローエディタの中にある TCP インプットノードを使ってみました:
2016021600


この TCP インプットノードはホスト名とポート番号を指定して、TCP サーバーソケットに直結してデータを受信する、というノードです。

実際に NodeRED で使ってみました。データが受信できることを確認したかったので、以下のような TCP インプットノード1つに、デバッグアウトプットノード1つをつなぐ、というシンプルなデータフローを作ります:
2016021601


TCP インプットノードをダブルクリックして属性を指定します。上から順にポート番号(下図では 12345)とホスト名を指定します。なおここで指定するホスト名のサーバーには、NodeRED サーバーから繋がる必要があります(つまりプライベートネットワーク上にある場合は正しくポートフォワードされている必要があり、加えてファイアウォールで指定ポートに穴を開けておく必要もあります)。また受け取るメッセージの形式を単体文字列に指定しています。この状態で NodeRED 上でデプロイします:
2016021602



一方の TCP サーバー側のアプリケーションを用意します。今回はこんなシンプルな Java アプリを、NodeRED のホストとして指定したサーバー上に作ってみました。(デフォルトで)12345 番ポートでクライアントを待ち受けて、接続があればメッセージを返す、というものです。待受ポート番号は起動時に指定することもできるようにしていますが、今回は使いません:
//. myTcpServer.java
import java.io.*;
import java.net.*;

public class myTcpServer{
  public static void main( String[] argv ) throws Exception{
    int port = 12345; //. デフォルトで待ち受けるポート番号
    try{
      //. 実行時のパラメータで待受ポート番号を変更できるようにする
      if( argv.length > 0 ){
        try{
          port = Integer.parseInt( argv[0] );
        }catch( Exception e ){
        }
      }

      //. サーバーソケット作成
      ServerSocket server = new ServerSocket( port );

      while( true ){
        Socket socket = server.accept(); //. 接続してくるクライアントを待つ

        //. 接続してきたら、メッセージを返す
        OutputStream output = socket.getOutputStream();
        String message = "Connected with port: " + port;
        byte[] out = message.getBytes( "UTF-8" );
        output.write( out );

        //. クライアントを破棄(ループで接続待ち状態へ戻る)
        socket.close();
      }
    }catch( Exception e ){
      e.printStackTrace();
    }
  }
}


これをコンパイルして、実行します:
# javac myTcpServer.java
# java myTcpServer

すると NodeRED 側のリクエストがこのサーバーアプリに繋がり、定期的にメッセージが返されることが分かります:
2016021603


デバッグメッセージをよく見ると、10秒おきにメッセージが送られてくることが分かります。これが TCP インプットノードの仕様なんですかね:
2016021604
ともあれ、TCP インプットノードを使って、TCP ポート直結でデータを受け取ることができる、ということが確認できました。

MQTT を使う必要はなく、ホスト名(IPアドレス)とTCP ポートから直結してデータを受け取ることもできる、というサンプルでした。ただ現実問題としてインターネットに公開されたホストからわざわざ TCP ポート直結のファイアウォールを空けてまでデータを受け取る必要があるか、と言われると、そこはやはり MQTT の方がいいのかなあ、とは思っています。まあどうしても MQTT が使えないようなケースに限った利用ケースかもしれません。


Node-RED だけで SQL Database(DB2) 内のデータを検索して結果を表示する、という仕組みを作ってみます。

IBM Bluemix などから提供されているオープンソース・データフローエディタである Node-RED は、ノードと呼ばれる機能単位のブロックを組み合わせて、ほぼコーディングをすることなくデータのワークフローを作れるウェブアプリケーションです。そのフローにデータベースを組み合わせて問い合わせしたり、結果を格納したり、といったことも少ない(或いはほとんどない)コーディングで実現できる、という便利なツールです。特に Bluemix と組わせることで IBM IoT Foundation と連携した MQTT システムを簡単に構築することが可能になります:
2016020101


データのフローを得意とする一方で、Node-RED 単体では UI はあまり得意ではありません。全くできないわけではないのですが、基本的には HTML などの知識を前提として、動的に HTML を作成するような形になります。むしろサーブレット的というか、UI を外出しした仕組みの1パーツとして利用するのが便利だと思っています。


例えば、現在 SQL DB 内に MYTABLE というテーブルが作られ、以下の様な状態になっているものとします。ここから ID 値が 'fff57f93.000a8' であるデータを検索する、ということを目的としてみます。この ID はパラメータとして与えるようにして、リクエストの度に変更できるようなものにします:
2016020101


普通に SQL を使うのであれば(言語によって JDBC などの設定をした上で)、SELECT * FROM MYTABLE WHERE ID = 'fff57f93.000a8' といった具合にクエリーを実行して結果を得ることになると思います。

この処理を NodeRED で行うにはどうすればよいでしょうか?そのためにはまずこの SQLDB に NodeRED からアクセスできるようになっている必要があります。Bluemix 環境であれば、目的の NodeRED プロジェクトに、この SQLDB を事前にバインドしておきます(下図ではサービス名が SQL Database-iv となっています。適宜みなさんの実際の環境と読み替えてください):
2016020101


バインドした上で NodeRED のパレット内の SQLDB を使います。なお、ここでの SQLDB ノードは(検索をしたいので)左右両方にノード接続点があるものを使います。
2016020102


そして以下の様な NodeRED データフローチャートを作ります。HTTP Request ノード、SQL DB ノード、JSON ノード、Template ノード、HTTP Response ノード、そして Debug ノードの6つを以下のようにつなぎます。以下、各ノードの属性を説明します:
2016020101


HTTP Request ノードでは Method として GET、そして URL として /query を指定します。この URL で指定した内容が実際に問い合わせをする際の URL になります:
2016020102


SQL DB ノードでは問い合わせ処理を記述します。Service では SQL DB のサービス名をプルダウンから選択し、Query には ID をキーに MYTABLE テーブルを検索したいので select * from MYTABLE where id = ? と入力します。そしてこの ? パラメータに代入される値を直前の HTTP Request ノードの URL パラメータから取得するようにします(Prepared Statement っぽい感じ)。HTTP Request ノードの URL パラメータの値は msg.req.query で取得できます。今回は id というパラメータにこの値が入っているよう想定するので、Parameter Markers の欄に msg.req.query.id と指定します:
2016020103


JSON ノードは JSON ブロックをそのまま配置するだけです(名前は適当に指定してください):
2016020104


そして Template ノードで検索結果を表示用に整形します。ここでは HTML 形式で、背景を薄い黄色にして表示してみましょう。以下の様な内容を入力します。HTML 内の {{payload}} 部分に検索結果が入り、その結果を msg.payload として返す、という指定をしていることになります:
2016020105


最後に HTTP Response ノードと debug ノードをそれぞれ配置します。この状態でフローを Deploy します:
2016020106


そしてウェブブラウザで、http://(Node-RED のあるサーバー)/query?id=(SQL DB から検索したいレコードのID値) を指定してアクセスします。成功すると指定した ID 値を持つレコードが JSON テキスト形式で、指定したフォーマット(背景が薄い黄色)で表示されるはずです:
2016020107


URL パラメータの id の値を別のレコードのものに変えると、指定したレコードの内容に変わるはずです:
2016020108


同時にこれらの検索結果は Node-RED 画面の debug タブ内にも表示されているはずです:
2016020109


上記例では結果を(わざわざ)HTML 化して出力しましたが、現実には JSON のまま返したり、XML 化して返す、といったサーブレット的な処理をする機会が多いと思います。要は見た目については Node-RED の外で管理すべきと考えています。


・・・と、そう思っていたのですが、最近になって Node-RED に UI パーツを追加できるようなノード部品が提供されたようです。ある程度用途は限られていますが、UI も Node-RED で作れるようになっていくのかもしれません:
https://github.com/andrei-tatar/node-red-contrib-ui


とりあえず、Node-RED だけでサーブレット的な検索機能を実装することもできそうだ、ということが分かりました。


このページのトップヘ