2018年5月19日土曜日

Windows 10上でNW.jsを使ったシリアル通信する

NW.jsで外部機器との通信をするとき、LinuxやMacは実績があるのだがWindowsではだめ、ということがとても多いようだ。

いくつか試して、とりあえずシリアル通信について、なんとか動かすことができたので手順を記録しておく。

NW.jsはNode.js + Chromiumなアプリ開発プラットフォーム。JavaScriptで書くところがミソだが世の中はElectronのほうが多いような気がしている。それはともかく。

通信に限らないとはいえ、C++によるネイテイブコードを含むNode.jsのモジュールはWindowsではやっかいだ。UNIXとWindowsで挙動の違う関数、Windows側にバグがある関数などがあって動かない、というのが多く見受けられるパターン。

また、ビルドにはnode-gypを使うようだが、 この環境作りにPython2.7とVisual Studio 2015が必要という、そこそこのハードルがある。VSもまともにインストールするとおおごとだし、node-gyp開発者側にPython3対応にする気が全くないようなので(GitHubのIssueのつれない感じからの類推)、node-gypだけの小さな閉鎖環境が望ましいことになる。というわけで、そんなパッケージが用意されたようだ。以下のコマンドを「管理者権限つきの」コマンドプロンプトに
npm install --global --production windows-build-tools
と入力して実行すると、ユーザのホームディレクトリ直下に、.windows-build-toolsというディレクトリができて、そこにPython2.7とVS2015のコマンドラインと必要なライブラリが一式入って、とてもありがたい。

ただし、すでにVS2015が入っていると、node-gypは一通り動くものの、NW.js対応にするときにやっかいなことになった(普段VSをC++の開発環境に使っているなら問題はないんだろうと思う)。詳しくはhttps://github.com/nodejs/node-gyp#installationのWindowsの注意書きを参照していただきたいが、「Common Tools fot C++」としてまとめられたツールやライブラリ群をVS2015を起動し、C++のプロジェクトを作る作業をすることでインストール開始させなければならない。

準備ができたところで、いきなりNW.js対応を作る前に、通常のNode.js用インストールをして動作テストをするのがよいと思う。これはnpmを使うなら、プロジェクトのディレクトリを作り、そのなかでnpm initしてpackage.jsonを作成したあと、、
npm install serialport
とする。node_modules以下に、大量の依存関係のあるモジュールとともに一式が入る。このあと適当に、サンプルプログラムなどを動かせばよいのだが、シリアルポートのデバイス名がLinuxやMacでは '/dev/tty.なんとか'のファイル名になっているところを、Windowsでは'COM3'などのように置き換える必要がある。もしシリアルポートがなくても、examples\mockings.js は仮想ポートデバイスを作って送受信のようなシミュレーションができるので、これで動作確認としてもいいかもしれない。

動作確認ができたあとは、NW.js用にリビルドが必要となる。Node.js用のままNW.jsで読み込もうとしても、はDLLエラーのようなことが生じて動かない。

NW.js用には、nw-gypという、node-gypのフォークモジュールがあって、それを入れておく必要があるらしい。また、node-pre-gypモジュールも入っていたほうが無難なようだ。

nw-gypの実行には、binding.gyp(C++のオブジェクトをJavaScriptのオブジェクトに結びつける設定ファイル)のある場所でなければならないようだ。そこで、node_modulesの下の、serialportの下にcdする。(cd node_modues\serialport)

このあたりになってくると、UNIXの操作に近くなってくるので、Gitをインストールし、その際に、コマンドプロンプトでMingWin由来のUNIX互換コマンドが動くよう設定しておくと気持ちが楽になると思う。GIt Bash上でやってもよいのかもしれないけれど、試してはいない。

リビルドのコマンドを実行する前に、NW.jsのバージョン(Node.jsやChromiumのバージョンではない)を確認しておく必要がある。NW.EXEを実行すれば、右下のいちばん上に、「nw.js v0.30.5」などのような表示が見えるので、それを控えておく。あるいは、ZIPを展開したフォルダ名が配布されたままならば、フォルダ名にバージョン番号が入っているので、それをみてもよい。

この先は、相性によって2つの方法を試してコンパイルが通るほうを選ぶようだ。また、python.exeが環境変数PATHに通っていなければいけないようなので(node-gypは%HOME%\.npmrcファイルの設定を読んでPython2.7の実行パスを決めてくれるが、nw-gypやnode-pre-gypは見ないようだ。Windowsでは、コマンドプロンプトに「set PATH=%PATH%;Python2.7のパス」のように、セミコロンで連結するのが流儀。%ではさんだ変数名は、変数参照を意味するので、既存のPATHに、Python2.7のパスを追加したことになる。

これでようやく実行。
nw-gyp rebuild --runtime=node-webkit --target=バージョン番号
または
node-pre-gyp rebuild --runtime=node-webkit --target=バージョン番号
のようだ。当方では今回は、nw-gypでコンパイルが通った。詳しくは、Qiitaの@sashimizakanaさんのエントリ「node-webkitでネイティブモジュールを使う」を参照。

バージョン番号を指定してリビルドしていることから、NW.jsのバージョンが変わるたびにリビルドが必要になるんだろうと思う。

動作チェックに使った、シリアルポートの一覧表示スクリプトを含むHTMLファイルを掲載しておく。ポートオブジェクトをJSONで、見えた数だけ列挙するというもの。console.log()も入れているのでデバッガのコンソールにも出る。元ネタはこちら。NW.js用ではないけれど、シンプルかつ丁寧に説明されていて、わかりやすいと思う。DOM操作は、VSCodeエディタの予測候補を見ながら適当に書いた。index.htmlのファイル名として、npm initしてpackage.jsonを作るときのmainに、デフォルトのindex.jsではなくindex.htmlと入力してある。

<!DOCTYPE html>
    <head>
        <title>Hello World!</title>
        <script>
            var SerialPort = require('serialport');
            // list serial ports:
            SerialPort.list(function (err, ports) {
                ports.forEach(function(port) {
                    portString = JSON.stringify(port);
                    console.log(portString);
                    title = document.getElementById('h1');
                    p = document.createElement('p');
                    text = document.createTextNode(portString);
                    document.body.appendChild(p).appendChild(text);
                });
            });
        </script>
    </head>
    <body>
        <h1 id="title">Hello World!</h1>
        <p id="port"></p>
    </body>
</html>
NW.jsからは、プロジェクトのディレクトリ名を引数に与えるのが簡単だと思う。

以上、ご参考まで。