non vorrei lavorare

2020年度からの小学校プログラミング教育の必修化を親として迎えるブロガーの書く、子供との日常

gRPCでmojaveに勝利してリモートからWebカメラを利用した

おはようございます。長男は忘れ物がひどく、よく奥さんに怒られて、マイクラ禁止になっています。@kjunichiです。

背景

macOSのMojaveからFaceTime HDカメラや、Webカメラ等のカメラデバイスへのアクセスが厳しくなり、 アプリ毎に設定が必要となってしまい、これまでのようにssh越しにリモートから利用することができなくなってしまった。

対策

あらかじめ、Termina.appでカメラにアクセスするコードを含むサーバプロセスを実行しておき、 SSHでリモートからアクセスした際はこのサーバプロセスにアクセスするクライアントを動かすことで、 Mojaveに怒られないようにする。

ちなみに、アクセス許可のないアプリ等からカメラデバイスにアクセスすると 以下の記事のようなことになる。

abrakatabura.hatenablog.com

クライアントとサーバ間の通信方式にgRPCを採用

クライアントとサーバ間の通信方式にgRPCを使えば、今時な実装でイケてるのでは?という安易な選択で いざ実装。

gRPCでストリームを使う

これまで Raspberry Piの温度をgRPCを使ってElectronで作ったデスクトップマスコットに表示させたmacOSのCPU温度をgRPCを使ってElectronで作ったデスクトップマスコットに表示させた と複数のgRPCをつかったリポジトリ保有しているものの、 .protoファイルがHelloWorldを流用してやり過ごしていた。

今回一念発起して、独自の.protoファイルかつStreamをつかったgRPCにチャレンジした。

gRPCでstreamを使うポイント

HelloWorldとともに公開されているRoute_guideのソースを見れば、大体わかる。 特にNode.js実装だと自分が慣れているのもあり、

  • サーバ側からstreamを返すパターン
  • クライアント側からstreamを送るパターン
  • クライアント、サーバともにstreamを送るパターン

のパターンがあり、これらのそれぞれのパターンに対応するメソッドがサンプルに 書かれている。

今回はクライアントからは通常のリクエスト、サーバ側からストリームというパターンで 作るのでサンプルの

function listFeatures(call) {

の関数を参考に作業を進めた。

クライアントからのリクエストを空にすることも可能

今回のアプリはWebカメラの画像を貰うだけなので、特にクライアントからサーバに渡すデータは 不要だったが、サンプルは何かしら入れているので、どうしたものかと一瞬悩んだが、

message Request {
}

という指定で.protoファイルを作ることが出来たので、スッキリした。

試行錯誤の末できた.protoファイル

こうして完成したのが、以下の.protoファイル

syntax = "proto3";

package camtype;

message Request {
}

message Response {
    bytes ppm = 1;
}

service Camtype {
    rpc Camtype (Request) returns (stream Response) {}
}

勘違いしていたStream

自分の中にはストリームというとバイトストリームのイメージが強く、 JSONが分割して送信されると誤解していたが、 .protoファイルで定義した構造体がNode.js実装の場合にはJSON形式でぼこぼこ返されるのだった。

message Response {
    bytes ppm = 1;
}

というppmという定義は、PPMファイルを適当にぶった切ってバイト列をで送るものだと決めつけていたが、PPMファイルをまるまる一回の

call.write({ ppm: data })

で送ることが出来てしまった。

当初はgRPCでStream指定をしたら、gRPC側で適当に細切れにして、クライアントに送信することを 来していたが、裏でどうなっているかは知らないが、表面的なAPIとしては、 サーバで1個streamを送信したら、クライアント側で1個受け取れる模様。

ちなみにクライアント側のでstreamを受け取るのは以下。

call.on('data',(response)=>{
    parsePpm(response.ppm)
})

サーバ側からStreamを送る際の注意

サーバ側

一連のstreamをwriteしたらendメソッドを呼び出す必要がある模様。

call.end()

クライアント側は

call.on('end',()=> {
    console.log(`end`)
})

として、サーバ側からのendを受け取ることができる。

成果物

github.com

学んだこと

これまでにgRPCには触ってはいたが、Helloworldの.protoファイルをそのまま流用しており、 今回初めて、自分のアプリ向けに.protoファイルを作成することを経験した。

Node.jsは動的に.protoファイルを扱えるので、試行錯誤しながら.protoファイルを作って 実装していくような今回のケースでは手軽で便利!

気になった事

サーバ側で、Webカメラの映像が安定することを期待してウェイトを入れているが、 これがどの程度長くしても怒られないのか、また、怒られる場合は、gRPCのタイムアウト になるのだろうか。

関連記事

7年前の記事