たねやつの木

Photos and Programming

ExpressでBroadlink RM mini3(黒豆)を制御する

f:id:ibuquicallig:20190313171642p:plain

こんにちは、たねやつです。前回の記事から続きです。

今回はRaspberry Pi上にNode.jsでAPIサーバーを建てて、HTTPリクエストを受け取ると前回記事で作成したリモコン操作用のコマンドを実行するようにします。

前の記事

前提

Raspberry Pi上でコマンドを実行するところの処理を実装する以外はvimなどを使用するよりも、Windows上にインストールしたVSCodeなどを使用したほうが楽かもしれません。デバッグもできるので詰まったときの問題解消が行いやすいです。

準備

Node.jsをインストールする

既にRaspberry Pi上に環境がある場合は不要です。

$ sudo apt-get update
$ sudo apt-get install -y nodejs npm
...

nodejsだけではnodejs用のパッケージ管理マネージャーのnpmはインストールされないので追加します。数分かかります。完了後に以下コマンドで確認します。

$ nodejs -v
v8.11.1

この環境作成時点でのnode.jsの最新LTSバージョンはv10まで上がっているがRaspberry Pi用のレポジトリ(Debianのレポジトリも?)ではそこまで到達していません。特に最新版でなくとも今回は問題ないのでそのままv8を使用していきます!

次にnpmのバージョンを上げておきます。

$ sudo npm -g install npm
...
$ npm -v
6.9.0

再帰的なコマンドになるがこれでアップグレードできます。バージョンが最新のものになっていない場合にはRaspberry Pi自体の再起動が必要かもしれません。

Expressなどその他パッケージをnpmでインストール

次にNode.jsで使用するパッケージをnpmでインストールしていきます。ExpressとはWEBアプリケーションを作るためのフレームワーク(便利ツール)です。

例えばhttp://raspi.taneyats.com/getTimeというページにアクセスすると現在時刻だけを返すページを作ったりすることができます。これをうまくpython-broadlinkと組み合わせることによって特定のページにアクセス(HTTPリクエストを投げる)すると家の電気をつけるというようなことが可能になります。Linux上のコマンドを実行することになるので言語の壁を越えて(JavaScript - Python)処理を実行することができます。

さらに拡張させるとIFTTTなどからHTTPリクエストを投げることができるので、どんどんほかのアプリや装置(Google Homeなど)と連携することができます。次の記事ではこの連携について紹介いたします。

以下のコマンドでExpressなどをインストールします。

$ sudo npm -g install express express-generator forever
...
+ express-generator@4.16.0
+ express@4.16.4
+ forever@0.15.3
added 300 packages from 187 contributors in 82.425s

express-generatorはExpressでWEBアプリケーションを作るためのテンプレートを作成することができます。foreverはExpressには直接関係しませんがNode.jsで動くサーバーをRaspberry Pi起動時に自動で動作させたり、異例に停止した場合に自動でサーバーを再起動させたりすることができます。

プロジェクトの作成

express [プロジェクト名]でプロジェクトを作成してくれます。myprojの部分は適宜名前を変えてください。

$ express myproj
create : ...

change directory:
     $ cd myproj

   install dependencies:
     $ npm install

   run the app:
     $ DEBUG=myproj:* npm start

作成が完了したら表示されている通りコマンドを実行していき依存パッケージなどをインストールします。npm installをプロジェクト直下で実行するとそのプロジェクトを動かすために必要なほかのパッケージを拾ってきます。run the appのコマンドは単純にnpm startでも大丈夫です。

$ cd pyproj
$ npm install
...
$ npm start
> node ./bin/www

スタートできたらブラウザからhttp://Raspberry Piのアドレス:3000にアクセスしてみてください。ポート番号の:3000を忘れずにつけてください。余談ですがブラウザからアクセスしようとすると基本的には80番ポートにアクセスします。明示的に指定することによってアクセス先のポートを変更できます。

簡素なWelcome to Expressというページが表示されるはずです。Raspberry Piのコンソールに戻るとアクセスしたログが残っているはずです。

GET / 200 1414.590 ms - 170
GET /stylesheets/style.css 200 16.159 ms - 111
GET /favicon.ico 404 104.069 ms - 1032

これでテンプレートから作成は完了です。gitでソースを管理するのであればこの段階で初期コミットをしておくといいかもしれません。

$ cd myproj
$ git init
$ git remote add origin [リポジトリのURL]
$ echo "node_modules" > .gitignore
$ git add .
$ git commit -m "init commit"
$ git push -u origin master

プログラムの作成

Expressのルーティング

まずはExpressでのルーティングとコマンドの実行などについて触れておきます。プロジェクト内のapp.jsというファイルを開くと以下のようになっています。

var createError = require('http-errors');
//...

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// ...

app.use('/', indexRouter);
app.use('/users', usersRouter);

// ...

var xxxRouterの部分で処理を変数に保持し、app.use()の部分で指定したURL(エンドポイント)に処理を割り当てます。ざっくりですがこのようになっています。

ですので新たに処理を追加したい場合にはこの2か所にの追加を行う必要があります。第一引数で指定しているエンドポイントにアクセスすると第二引数の処理が走ります。

http://hogehoge/userにアクセスするとuserRouterで指定したjavaScriptの処理が実行されます。一つ目の/は特にエンドポイントを指定せずにドメイン名やIPアドレスでアクセスしたときに表示されます。先ほど確認したWelcome to Expressの画面はまさにこの処理によって表示されたものとなります。

./routes/indexと記述している箇所が処理の本体になります。プロジェクト内を見てみると確かに/routes/index.jsというファイルがありますね!

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

pug(jadeから改名)というHTML生成用の別のエンジンでHTML構文が生成されていますがここでhttp://Raspberry Piのアドレス:3000にアクセスしたときの処理が記述されています。

dateコマンドの結果を返すページを作成する

それでは早速簡単な処理を作ってみます。Node.jsを実行しているマシンでのdateコマンドを表示するだけのページを作ります。

まずはapp.jsにルーティング処理を追加します。

var createError = require('http-errors');
//...

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
// 以下を追加
var getDateRouter = require('./routes/getDate');

var app = express();

//...

app.use('/', indexRouter);
app.use('/users', usersRouter);
// 以下を追加
app.use('/getDate', getDateRouter);

既にテンプレートを生成した段階で参考になるものは存在しているのでそれに真似て書きます。変数名なんかは一新してしまってもいいかもしれません(ルーティング処理の処理であることは明白なので)

次にアクセス時の処理を追加します。/routes/index.jsと同じ場所にgetDate.jsという名前で作成します。作成するというよりもindex.jsを複製して名前を変えたほうが手間が省けます(゜_゜)

ファイルを開いて一旦index.js特有の処理を削除します。

var express = require('express');
var router = express.Router();

/* */
router.get('/', function(req, res, next) {
    
});

module.exports = router;

次にdateコマンドを実行する処理を書いていきます。Node.jsではLinux上のコマンドを実行するためにはchild_processというモジュールのexec()という関数を使うことで簡単に実行できます。外部のモジュールを使うためにはrequireで取得して変数に格納するなどして使用します。(pythonのimportのような感じです。)

requireの部分を足すとこんな感じです。

var express = require('express');
var exec = require('child_process').exec;
var router = express.Router();

router.get('/', function(req, res, next) {

});

module.exports = router;

exec()の第一引数には実行したいコマンドをStringで指定し、第二引数には結果などを保持したコールバック関数を指定します。コールバック関数に関しては以下を参考にしてください。

Promiseなどで処理の順序を制御するともっとすんなりしたコードになりますがとりあえずこのまま進めていきます。

コールバック関数の第一引数にはエラーオブジェクト(エラーかどうかを判定するために使うはず)、第二引数には標準出力(コマンドを実行し成功したときに表示される内容)、第三引数には標準エラー出力(コマンドを実行しエラーが発生したときに表示される内容)が渡ってきます。

今回はとりあえず動かすだけなのでエラー判定は行わず、標準出力だけを使用しページに表示します。最終的なソースはこんな感じになります。

var express = require('express');
var exec = require('child_process').exec;
var router = express.Router();

/** dateの実行結果を表示します */
router.get('/', function(req, res, next) {
  exec('date', function(err, stdout, stderr) {
    res.send(stdout);
  });
});

module.exports = router;

res.send()に指定したものが画面に表示されます。APIサーバーとして使用するときにはここにオブジェクト指定して返し、クライアント側でJSON.parse()して使用するという感じがよくある流れだと思います。

一点注意ですが、Windows上で実行するdateコマンドとLinux(Raspberry Pi)上で実行するものは若干異なります。

Windows上でNode.jsを動かしている場合はコマンドにdate /tと指定してください。オプションを指定せずに実行すると現在時刻変更用のプロンプトにより処理が完了せずページが表示できません。

> date
現在の日付: 2019/03/11
新しい日付を入力してください: (年-月-日) _
(ここで入力待ちとなり処理が完了しない)

Linux上で行う場合には問題ありません。




これでhttp://[Raspberry Piのアドレス]:3000/getDateにアクセスしてみるとアクセスしたときの現在日時(Windowsの場合現在日付)が表示されているはずです。

404 Not Foundが表示される場合はルーティングが間違っている場合があるかもしれません。app.jsの変数名やファイル名を確認してください。ブラウザの読み込みが完了しない場合にはgetDate.jsの処理が間違っていてres.send()にまで到達できていない可能性があります。

前回作成したRM mini3用のコマンドを実行する

ここからが本題となります!Linuxコマンドを実行することができたので前回コマンドラインから実行したコマンドで処理を作成してみましょう!

./broadlink_cli --device @dev --send @sig/light_on

こんな感じのコマンドを作成しましたね!(^^)! light_onでシーリングライトをONにする想定で進めていきます。ほかの家電の動作を制御する場合は適宜ファイル名や変数名などを置き換えて作成してください。

まずはpython-broadlinkへのシンボリックリンクを今回のExpressのプロジェクト内に作成して簡単にアクセスできるようにします。シンボリックはWindowsでのショートカットのようなものです。

$ ln -s [broadlink_cliがあるディレクトリの絶対パス] bl_cli
$ ls -la
...
lrwxrwxrwx  1 hoge hoge   24 Mar 11 20:24 bl_cli -> ../python-broadlink/cli/
...

相対パスでも大丈夫です。リンク名はbl_cliとしてコードに書く量をなるべく少なくしていきます。次にapp.jsに追記していきます。getDateを追加したときと同じなので簡単ですね!

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var getDateRouter = require('./routes/getDate');
var lightOnRouter = require('./routes/lightOn');

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/getDate', getDateRouter);
app.use('/lightOn', lightOnRouter);

次にlightOn.jsを作成していきます。getDate.jsをコピーして使用します。

var express = require('express');
var exec = require('child_process').exec;
var router = express.Router();

var command = './bl_cli/broadlink_cli --device @bl_cli/dev --send @bl_cli/sig/light_on';
var str;

/** シーリングライトをONにします */
router.get('/', function(req, res, next) {
  exec(command, function(err, stdout, stderr) {
    if (err) {
        str = 'error occurred';
    } else {
        str = 'success';
    }
    res.send('str');
  });
});

module.exports = router;

コマンドを変数化して見やすくしています。またシンボリックリンクを使用する場合は適切にパスを追加してください。

エラーが発生している場合には画面上にerror occurredと表示します。表示しなくとも家電がシーリングライトが動作しているかどうかで判断はできますが。。。http://Raspberry Piのアドレス:3000/lightOnにブラウザからアクセスして結果を確認してください。

以上でシーリングライトをONにする処理は完成です。OFFにする処理もちょろっと変えるだけなのでパパっと作れるかと思います。

ポート番号を変更する

プロジェクト内のbin/www3000の部分を変更するとデフォルトのポート番号が変更できます。またはnpm start時に環境変数を設定することでも変更可能です。

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

起動時にポート番号を指定する場合には、以下のコマンドで起動します。上記の変更した箇所よりもこちらのほうが優先されます。

$ PORT=xxxx npm start       ← Linux(Raspberry Pi)の場合
> $env:PORT=xxxx; npm start ← Windowsの場合

環境変数の指定はnpm startで実行されるコマンドに混ぜ込んでしまえば毎度指定する必要がなくなります。npm startで実際に実行されるコマンドはプロジェクト内のpackage.jsonというファイルに記載されていて、script.startという部分です。ここに上記のように変数を使いしてあげるとOKです。

最後に

ここまででURLにアクセスするだけでリモコン操作ができるようになりました。別のWEBページにこのURLにアクセスするボタンなどを置くことで(WEBサーバー側からは)Raspberry Piをまったく意識することなくリモコン操作できるようになります。

Expressを使用すると簡単に処理をルーティングすることができその名の通り特急でプログラムを作り上げることができるのが魅力ですね!exec()の部分をPromiseで実装することもできるモジュールもあるのですがとりあえずコールバック関数のまま実装しました。

次の記事

IFTTTを使用してGoogle HomeからAPIを実行できるようにします。これでようやくGoogle Homeからどんなリモコンでも操作できるようになります。

参考

https://github.com/foreverjs/forever/pull/627

https://www.gesource.jp/weblog/?p=8228

https://github.com/foreverjs/forever/issues/540