RaspberryPi+Node.js+socket.ioでブラウザからgpioをリアルタイムに制御する

大分県の技術・市場交流プラザ大分の企画により依頼されたIoT製品開発案件の中で
ラズパイでwebサーバを構築し,gpio状態をローカルネットワーク上のandroidのchromeから監視するシステムを作ったで開発メモ．

# 環境

- ハード:Raspberry Pi Model3B
- OS:raspbian stretch

# ブラウザからgpio状態を見るには
主に次の2つの方法があることがわかりました.

1. pythonでhttpサーバ構築,リクエストが来たらwiringpiでgpio状態を取得してレスポンス
1. node.js+socket.ioを利用し双方向通信でgpio状態をサーバから送信

pythonでhttpサーバを構築する方法は最初に思いついた方法だが,読み込みたいときにページを再読み込みしないといけないのが気に食わなかった．
node.jsとsocket.ioを使えばhttpで双方向通信を可能にしリアルタイムにデータを送受信できることが分かりましたが、
node.jsとsocket.ioに関して全くの無知だったのでざっくり勉強しながら実装してみました.

# node.jsとは

> [Node.js を5分で大雑把に理解する](https://qiita.com/hshimo/items/1ecb7ed1b567aacbe559)

わかったこと

- サーバーサイドのjavascript
- サーバーサイドなのでfilestreamやサーバー側コマンドを扱うことができる.
- apache等のHTTPサーバの上に走らせるフレームワークではなく,node自体がHTTPリクエストを受け付ける

# socket.ioとは
WebSocket等の非同期・双方向通信プロトコルをnode.jsから利用できるようにしたモジュール.

> [WebSocketについて調べてみた。](https://qiita.com/south37/items/6f92d4268fe676347160)

こちらの記事の前半だけ大雑把に理解した感じだと,WebSocketというプロトコルはHTTPにリアルタイムな双方向通信が行えるような機能をちょっと上乗せしたプロトコルの様です.そのためブラウザ(クライアント)が対応している必要があります.

# nvmからnodeインストール
nvm(node version manager)は名前の通りnodejsのバージョンコントロールがしやすくなるツールです.これを使って最新のnodeをインストールしていきます.
githubからnvmをクローンして,nvmを利用可能にします.

```
$ git clone https://github.com/creationix/nvm.git ~/.nvm

$ source ~/.nvm/nvm.sh
```

gitがインストールされてないときはあらかじめ`apt-get install git`でインストールしてください.
nvmから目的のバージョンのnodeをインストール.nodeは奇数バージョンが最新版,偶数バージョンがLTS版(Long-Term Support;安定版)のようなので,今回は現時点のLTS版で最新の8.9.4にします.

```
$ nvm install 8.9.4
```
`$ node -v`でバージョン確認してインストールしたバージョンが出れば成功です
次にデフォルトのバージョンを設定します.

```
$ nvm alias default v8.9.4
default -> v8.9.4
```

再起動時に再度sourceコマンドを打たなくてもいいようにvim等のテキストエディタで~/.bash_profileを編集し,以下を追記します.

```
$ vim ~/.bash_profile
```

```shell:~/.bash_profile
if [[ -s ~/.nvm/nvm.sh ]];
 then source ~/.nvm/nvm.sh
fi
```

これでnodeのインストールと初期設定は完了です.
# nodejsでHello World
適当なフォルダに以下のファイルを作成し保存

```js:helloworld.js
var http = require('http');

//引数にhttpリクエストが来た時のコールバック関数を指定
var server = http.createServer(serveron);

//使用するポート番号
//外部からアクセスするときはファイアウォールを設定しておくこと
var port = 8080;
//アドレス
var addr = '0.0.0.0';

//httpリクエストが来た時の処理を担当するコールバック関数を定義
//主な動作はここに書いていく.
function serveron(req, res){
   //アイコン取得リクエストは無視
   if(req.url == '/favicon.ico'){
      return;
   }

   // リクエスト内容をコンソールログ出力
   console.log(req.method);

   //レスポンス
   res.writeHead(200, {'Content-Type' : 'text/html'});
   res.write('Hello World!\n');
   res.end();
}

// サーバースタート
server.listen(port, addr);
// コンソールログ出力
console.log('node http server started.');
```

保存したディレクトリで

```
$ node helloworld.js
```

コマンドでスクリプトを実行.ブラウザで127.0.0.1:8080にアクセスしてhello world!が表示されれば成功.


# npmからSocket.ioインストール
nodejsをインストールするとnpm(node package manager)というのがついてきます.nvmと似ていて紛らわしいですがこちらはnodejsで使えるパッケージを簡単に管理できるツールです.pythonでいうpipみたいなものです.
`npm install [package-name]`コマンドで簡単にインストールできます.
注意すべきなのは通常npmを使ってインストールするパッケージはカレントディレクトリに作られるnode_modulesフォルダに保存されるということです.(プロジェクトごとに必要なパッケージだけ使うことができる)
今回はグローバルに使えるようにsocket.ioをインストールするとして,インストールコマンドに-gオプションを付けて以下のようにします.

```
$ npm install -g socket.io
```

これでnodejs+socket.ioを使うための準備完了です.

# 実装方法
とりあえず,GPIO0番ピンに接続されたスイッチのON/OFFをブラウザから見れるようにします.

サーバー側

1. child_processでGPIO0番ピンの状態を取得するコンソールコマンドを実行して標準出力を得る→変数に代入
1. setIntervalで常に一定時間ごとにこのコマンドを実行し変数を更新する
1. クライアントとのwebsocketコネクションが確立している間setIntervalで一定時間ごとに変数の値(=スイッチの状態)を送信

GPIO状態取得コマンドはwiringpiのgpioコマンドを使います.`$ gpio read [pin]` で標準出力に1/0が出ます.
クライアント側

1. サーバーとのwebsocketコネクション確立
1. サーバーからGPIO状態データを受信したらHTMLを書き換えてGPIO状態の表示を切り替える

# 実装

コードはサーバー用のjsファイルとクライアント用のhtmlファイルの2つ用意し同じディレクトリに保存します.

```js:server.js
// httpのインポート
var http = require('http');
// socket.ioのインポート
var socketio = require('socket.io');
// fs:ファイルストリームモジュールインポート
var fs = require('fs');
// サーバコマンドを扱うためのモジュール
var exec = require('child_process').exec;

var host = '0.0.0.0';
var port = 8080;

// スイッチの接続されてるピン番号
// 番号はwiringPiのgpio番号に準ずる
var sw_pin = 0;

server = http.createServer(serveron);

function serveron(req,res) {
	// アイコン取得リクエストは無視
	if(req.url == '/favicon.ico'){
		return;
	}

	console.log(req.method);

	res.writeHead(200, {'Content-Type' : 'text/html'});

	// 最後のレスポンスは以下のようにres.end()の引数に書いてもいい.
	// fs.readFileSync()はhtmlファイルの中身を(同期で)レスポンスしてくれる
	res.end(fs.readFileSync(__dirname + '/index.html', 'utf-8'));
}

// httpサーバースタート
server.listen(port);

// コンソールログ出力
console.log('node http server started.');

// websocketリスン開始
var io = socketio.listen(server);

// スイッチの状態を入れる変数を宣言
var sw = '0';

// setInterval(function, time) :time(ms)毎にfunction実行
setInterval(function(){
	// wiringPiの `gpio read [pin]`コマンドを実行して標準出力を得る
	exec('gpio read ' + sw_pin, function(err, stdout, stderr){
		console.log('switch status:' + stdout);
		sw = stdout;
	});
}, 1000);

// クライアントからwebsocketコネクションがあった時に呼ばれる関数を定義
io.sockets.on('connection', function(socket){
	console.log('socket connected.');
	setInterval(function(){
		// スイッチ状態の値を'switch_data'という名前で送信
		socket.emit('switch_data', {value : sw});
	}, 1000);
});
```

```html:index.html
<!DOCTYPE html>
<html lang='ja'>
	<head>
		<!-- クライアント側socket.ioを読み込む -->
		<script type="text/javascript" src="/socket.io/socket.io.js"></script>

		<script>
			// websocketコネクションを確立
			var socket = io.connect();
			// サーバーからswitch_dataのデータが来たら呼ばれるコールバック関数を定義
			socket.on("switch_data", function(data){
				// htmlの表示を書き換える
				document.getElementById("switch_status").innerHTML = data.value;
			});
		</script>
	</head>
	<body>
		<p> SWITCH STATUS:</p>
		<div id="switch_status"></div>
	</body>
</html>
```

保存したら`$ node server.js`でサーバー起動.ブラウザからアクセスしてスイッチの状態が1/0で表示され,さらにラズパイに接続されたスイッチを切り替えるとブラウザ側でページを切り替えることなく1/0が変わることが確認できました.

コードの詳しい解説やsocket.ioの詳しい使い方は説明しません(細かいトコまで自分もよくわかっていないので).
コードとコメントでデータ送受信の仕方はわかると思います.ポイントは`on`で受信,`emit`で送信です.
# まとめ
とりあえずラズパイ上にwebサーバーを構築してブラウザからリアルタイムにGPIO状態を見ることができました.
今回はラズパイのGPIO状態をブラウザから見ることが目的でしたが,この方法でブラウザからのGPIOの制御も出来ると思うのでどなたか興味のある方はやってみてください!