gulpとbrowsersyncを軽量なリアルタイムモニターの例

# gulpとbrowsersyncを軽量なリアルタイムモニターに採用した理由

ここで紹介するのは、生成された画像の生成に合わせてwebページを自動更新する簡単な方法である。ご利益などをざっとまとめるとこんな感じ。

- リアルタイム性は欲しいが、データ量や計算量が多いため画像をパパッと切り替えるくらいの負荷に抑えたい。
- BrowserSyncは単体で自動的にサーバーとして動く仕組みを持つ。それにGulpというタスク自動化ツールで微調節できる格好にする。簡単に実現できる(2020.3.7時点の話) 
- BrowserSyncとGulpについては、他によくまとまった記事があるのでそちらの参照されたい。この記事は、ほぼミニマムな部分の紹介であるが簡単に高機能に拡張できる。
    - browserSync : 
        - https://qiita.com/ta-ke-no-bu/items/e1972ff0f2ddfaf84b2b
        - https://ics.media/entry/3405/
    - gulp : 
        - https://ics.media/entry/3290/
        - https://www.webcreatorbox.com/tech/gulp
- 実験や試験によっては、OSや環境構築がコロコロ変わるので、OS非依存で環境設定の作業がミニマムになる(=インストールでハマらない)と考えた。
- BrowserSyncの機能でサーバーが立ち上がる。同じネットワーク内であれば別のマシンから更新画面を表示し、必要ならデータ授受や後段の処理も可能になる。
       
もし、軽量でリアルタイム性かつグリグリとプロットをいじったりする機能が欲しい場合は、[plotly](https://plot.ly/python/) や、[pyqtgraph](https://watlab-blog.com/2019/10/20/pyqtgraph-install/) の採用がオススメである。

単にブラウザを一定間隔で更新したいだけであればhtmlに直接書くことで対応できる。
https://www.mitsue.co.jp/knowledge/blog/frontend/201806/29_1525.html 
BrowserSyncのご利益はタスクランナーと簡単につなげることができるのと、簡単なサーバーが立ち上がる点です。browsersyncを実行したディレクトリ以下のファイルにリモートで可能になるので、上流で一発走らせておけば、それ以下にあるログファイルや設定ファイルを同じネットワークであれば見れることになります。複数のPCで閲覧した時に同じ内容を保証する必要がある場合には使いどころがあるでしょう(きっと!!)

## インストール方法

- node.js 
    - nodebrewでインストール
    - 参考ページ: https://qiita.com/wagi0716/items/94193a80502f9d81a9e0

macの場合は、nodebrewでインストールするとバージョン管理が楽な模様。windowsは本家からインストールする。

``` bash:
$ curl -L git.io/nodebrew | perl - setup
$ nodebrew install-binary latest # 必要なPATHを通す
$ nodebrew ls # バージョンを確認                                                            
v13.10.1
current: none
$ nodebrew use v13.10.1 # バージョンを指定する                                                                  use v13.10.1
$ node -v                                                                                      v13.10.1
$ which npm  # npmもパスが通ってることを確認                                                     
/Users/syamada/.nodebrew/current/bin/npm  
```

- [gulp](https://gulpjs.com) 
    - npm install --save-dev gulp # ローカル
    - npm install -g gulp # グローバル
- [BrowserSync](https://browsersync.io) 
    - npm install --save-dev browser-sync # ローカル
    - npm install -g browser-sync # グローバル

gulp 関係は、グローバルなモノからローカルを呼び出す仕様なので、両方のインストールが必要となる。

- [node.js](https://nodejs.org/en/)
    - GUIでインストールも可能だが、macの場合は nodebrewが良いらしい。
(動作確認は、node 13.10.1, npm 6.13.7, gulp 2.2.0(CLI), local(4.0.2), browser-sync 2.26.7)

### ファイルの準備

index.html と gulpfile.js を用意する。

``` html:index.html
<html><head>
<title>Real-time monitor</title>
</head>
<body>
This is a real-time monitor page. <br>
</body></html>
```

index.htmlの中身は何でも良い。

``` js:gulpfile.js 
var gulp        = require('gulp');
var browserSync = require('browser-sync');

gulp.task('browser-sync', function() {
    browserSync({
        files: ["index.html"],
        port : 3010,
        server: {
            baseDir: "./" // Change this to your web root dir
        }
    });
});
// Default task to be run with `gulp`
gulp.task('default', gulp.series(gulp.parallel("browser-sync"), function(){
	}));

```

gulpのversion4に準拠したdefaultの書き方をしている。v3からdefaultの書き方が変わったことに注意。デフォルトのポートを 3010 に変更している。ここを書き換えれば任意のポートを使うことができる。

## 実行結果

これで、web ページが開ければ動作はOKである。

``` bash:
$ gulp                                                              
[23:57:48] Using gulpfile ~/work/software/browser-sync/gulpfile.js
[23:57:48] Starting 'default'...
[23:57:48] Starting 'browser-sync'...
[Browsersync] Access URLs:
 --------------------------------------
       Local: http://localhost:3010
    External: http://192.168.179.3:3010
 --------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 --------------------------------------
[Browsersync] Serving files from: ./
[Browsersync] Watching files...
```

これが動いたらあと少し。更新したいタイミングで、htmlを書き換えるか、画像の変更をwatchするような仕組みと組み合わせれば自動的に更新される。

## 自動更新のサンプルコード

ここでは、sin, cos の図を python の schedule を使って時事刻々と更新する例を示す。

ディレクトリ構造は、

- gulpfile.js # 上で作ったもの
- index.html # programs で自動生成される
- node_modules # 上で生成されたもの
- programs # このディレクトリの下に下記のpythonコードを置く。

を前提とする。programs を gulpfile.js よりも下に置くのは、デフォルトで localhostの一番上の階層が gulpfile.jsのある場所になるので、それ以下にファイルがあると何も不自由なくファイルにアクセスできるためである。

### 画像生成とindex.htmlの更新スクリプト

``` python:

#!/usr/bin/env python

__version__=  '1.0' # 2020.3.7, for BUSV

import argparse 
import glob
import schedule
import time
from datetime import datetime
datadir="rawdata"
serverdir="../"
indexfile=serverdir + "index.html"
serverdirtohere="./programs"

html_template = """
<html><head>
<title>Real Time Monitor</title></head>
<body bgcolor="#ccffcc"><body>
<h1> Real Time Monitor </h1>
<hr align=left size=2 color="#008000">
<h2> Updated on {DATE}</h2>
<img src="{FIGNAME}" alt="plot1" title="plot1" width="800"> 
<br></body></html>
"""

import matplotlib.pyplot as plt
import numpy as np

g_time=0
dt = 50
Npoints = 200

def mk_figure(debug=False):
	global g_time
	g_time = g_time + 1
	nowstr = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
	nowfile = datetime.now().strftime("%Y%m%dT%H%M%S")
	figname = nowfile + ".png"	
	x = np.linspace(g_time, g_time+dt, Npoints)
	y1 = np.sin(x)
	y2 = np.cos(x)	

	fig = plt.figure(figsize=(8, 6))	
	plt.title(nowstr)
	plt.plot(x, y1, label="sin")
	plt.plot(x, y2, label="cos")
	plt.legend(bbox_to_anchor=(0., 1.01, 1., 0.01), loc='lower left',ncol=10, borderaxespad=0.,fontsize=8)		
	plt.savefig(figname)
	figpathtoserver= serverdirtohere + "/" + figname
	plt.close()	
	# update index.html 
	body=html_template.format(DATE=nowstr,FIGNAME=figpathtoserver)  
	with open(indexfile,"w") as f:
		f.write(body)

def main():
	parser = argparse.ArgumentParser(description="This is to do FFT and update index.html")
	parser.add_argument('-d','--debug', action='store_true')	
	parser.add_argument('-t','--testrun', action='store_true')		
	parser.add_argument('-p','--period', type=float, default=1) 	
	args = parser.parse_args()	
	period = args.period
	testrun = args.testrun
	debug = args.debug

	if args.testrun:
		mk_figure(debug=debug)
	else:
		print("do mk_figure every " + str(period) + " (sec)")	
		schedule.every(period).seconds.do(mk_figure,debug=debug)
		while True:
		    schedule.run_pending()
		    time.sleep(1)		
	
if __name__ == '__main__':
    main()
```

- 引数なしで走らせると1秒ごとに図を生成し、index.htmlを書き出す。

### 動作結果

まず、python スクリプトを走らせる。
``` sh:
$ python qiita_test_gulp_browsersync.py
do mk_figure every 1 (sec)
```
走るのを確認する。図が1秒おきに生成されて、htmlが更新されている。

次に、一つ上のディレクトリに移動し、gulpを走らせる。gulpは gulpfile.js があるディレクトリで gulp と打つだけ。引数もないので簡単。

``` sh:
$ gulp                                                    
[21:12:19] Using gulpfile ~/work/realtime_monitor_qiita/gulpfile.js
[21:12:19] Starting 'default'...
[21:12:19] Starting 'browser-sync'...
[Browsersync] Access URLs:
 --------------------------------------
       Local: http://localhost:3010
    External: http://192.168.179.3:3010
 --------------------------------------
          UI: http://localhost:3001
 UI External: http://localhost:3001
 --------------------------------------
[Browsersync] Serving files from: ./
[Browsersync] Watching files...
^A[Browsersync] Reloading Browsers...
[Browsersync] Reloading Browsers...
[Browsersync] Reloading Browsers...

```

これで、index.html が1秒おきに更新される。

![qiita_gulp_bworsersync_p1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/179652/698f369d-6acf-3378-d6a2-8b585ee8464a.png)

一応、動画も作成しておきました。 http://www-x.phys.se.tmu.ac.jp/~syamada/qiita/qiita_gulp_browsersync.mp4

gulpは常に index.html を見ており、これが更新されるとリロードしてくれるので、pythonで例えばファイルをリロードする関数を用意しておけば、ここで強制的にリロードできる。gulpを使って画像ファイルの更新をモニターすることも可能だが、pythonのsheduleでcronを走らせる場合はindex.htmlを更新した方が楽であろう。

``` python:index.htmlをリロードするため関数
def reload_indexhtml():
	f=open(indexfile,"r")
	body=f.read()
	f=open(indexfile,"w")
	f.write(body)
	f.close()
```

### 最後に

これを少し改良すれば、簡単な状態監視システムなども作れるはず。実際にコーディング中にgulpを立ち上げ置くとhtmlの変更箇所が反映されて、本来のBrowserSyncの機能を活用しただけではあるが、開発スピードが上がるのは実感できた。