chirimen-oh / chirimen

CHIRIMEN for Raspberry Pi
https://chirimen.org/chirimen/
13 stars 17 forks source link

[Hello Remote World] pubsub サーバを使った連携サンプルを用意する #91

Closed dynamis closed 4 years ago

dynamis commented 4 years ago

暫く前から Slack で悟さんと議論しているものですが、websocket の中継 pubsub サーバを介して CHIRIMEN デバイス間のリモート接続を簡単に可能なサンプルを用意したい。

IoT だから、ということもあるが、コロナな昨今リモート通信して連携することでリモート拠点間で1つの作品を作成可能にすることも意図した話。

@satakagi wrote:

CHIRIMENのチュートリアル・Examplesに、CHIRIMENでつくったデバイスアプリ間や、CHIRIMENアプリと、普通のウェブコンテンツ間の連携(遠隔操作や遠隔センシングネタ)を追加していきたいと思ってます。

  • Hello Remote World
    • CHIRIMEN1:タクトスイッチ
    • Web上の中継サービス
    • CHIRIMEN2:LED
    • タクトスイッチを押すとLEDが点灯
  • Web Magic Hand
    • CHIRIMEN1:加速度(姿勢)センサー
    • Web上の中継サービス
    • CHIRIMEN2:サーボモーター
    • 加速度センサを傾けるとそれに応じてサーボが動く

中継サービスは、 https://blog.mbaas.nifcloud.com/entry/2020/05/21/181532 で紹介されているような無料のwebSocketリレーを使うとすぐ使えて良いかな?

そこでも紹介されてるが中継サービスとしては pusher が十分な無料枠がクレジットカード登録不要かつ容易に使えてかなりよさそうと @kou029w などと話していました:

https://pusher.com/

dynamis commented 4 years ago

オープン・中立性という意味では複数のサービスに対応、シンプルに fly.io などで自前でさっとサーバを動かすことも可能なように

ということが出来るのが理想。取りあえず自前で立てるのは fly.io などで簡単にできるだろうとはいえ初心者向けでないと思うとまずは前者の方が先に用意するところかな。

そしてそこでサポートすべき機能だが、pusher.com の仕様を見ているとこんな感じかなと思う:

  1. サービスの認証キーで認証
    • let relayServer = new RelayServer('Server-Type', 'YOUR_APP_KEY')
    • Server-Type は pusher.com とか。API Key が単一文字列だけじゃなくユーザ名とペアになってるサービスがあったらどうしようかな。そもそも API キーをついコミットして晒してしまいそうになるのはどうしようかな。
    • 認証せず全 public なサーバであれば認証情報は無しでも良いが API としては用意しておく
  2. チャンネル名で subscribe
    • let channel = relayServer.subscribe('channel-name')
    • チャンネルは public はナシで認証必須な private だけでも良い
  3. subscribe したチャンネルのメッセージを受信する関数をバインド&クライアントイベントをトリガ
    • channel.on('event-type', () => { channel.emit('client-event-name', { my: data }) })
    • メソッド名は on なのか bind なのか addEventListener なのかは迷う (addEventListener である場合はちゃんと addEventListener の仕様に合わせた引数リストをサポートするとか実装に配慮が必要と思う)
    • 同じく emit なのか trigger なのか迷う。まぁ websocket 業界標準の socket.io の真似して on/emit で良いんじゃないかという気持ち

デフォルト public じゃなくてデフォルト private というのは最小機能に一見反している気がするが、pusher ではサーバ側からじゃなくクライアント (JS/CHIRIMEN 側) からのイベントは public チャンネルで使えない制約が課せられていることを考えると、private 側が公約数の機能と言える:

https://pusher.com/docs/channels/using_channels/events#triggering-client-events

pusher 以外のサービスの API の確認と、MQTT over websocket サーババックエンドのケースも検討しておくと良さそう。

satakagi commented 4 years ago

public チャンネルで使えない クライアントイベントはプライベートチャネルとプレゼンスチャネルでのみトリガーできます

プライベートチャネルとプレゼンスチャネルは、ユーザー認証が必要 https://pusher.com/docs/channels/using_channels/private-channels

認証するには、認証用の動的な機構がサーバサイドに必要 https://pusher.com/docs/channels/server_api/authenticating-users

ということでPusherでデバイス間で通信するのはかなりハードル高め?

satakagi commented 4 years ago

Pusherは、ざっと試した限り、静的なページだけでクライアント間のpub-subはつくれないようです。(簡単なphpが使えるホスティングでもかなり厳しい感じがする)

ということで、Pusherひとまず諦めて、確認ができた他の三つ(websocket.in, achex, scaledrone)でやってみますかね。

satakagi commented 4 years ago

https://svg2.mbsrv.net/chirimen/webSocket/tester.html

3つのサービスを切り替え、同じAPIから使えるライブラリのドラフトができました。

APIの仕様は、ほぼ @dynamis さんが書いた感じ。 ただ、メッセージのin/outだけということにして、

channel.onMessage(callBackFunc(message){...});
channel.sendMessage(message);

としてます。 あと、subscribeはPromiseになり、

let channel = await relayServer.subscribe('channel-name');

WebIDL


enum ServiceName { "achex", "websocketin" , "websocket.in" , "scaledrone" };

[Exposed=(Window)]
interface RelayServer {
  constructor(ServiceName serviceName, USVString serviceToken);
  Promise<Channel> subscribe(optional USVString channelName);
}

interface Channel {
  readonly attribute USVString serverName;
  void onMessage(MessageHandler handler);
  void sendMessage(USVString or object );
};

callback interface MessageHandler {
  void handleMessage(object message);
};

ライブラリ https://svg2.mbsrv.net/chirimen/webSocket/RelayServer.js

サンプルコード https://svg2.mbsrv.net/chirimen/webSocket/chirimenRemote.html

dynamis commented 4 years ago

認証するには、認証用の動的な機構がサーバサイドに必要 https://pusher.com/docs/channels/server_api/authenticating-users

ということでPusherでデバイス間で通信するのはかなりハードル高め?

すみません、見落としてました。認証させるときは authEndpoint をしていして pusher のサーバから指定エンドポイントに認証リクエストが飛ぶ都合、クライアント JS 側に認証のためのシークレットなどを持たせて処理させる形での認証も実装できず、サンプルは用意されているとはいえ認証サーバを立てることは避けられませんでした。

pusher という名前の通り基本的には push 通知を支援するサービスという理解が正しそうです。WS 以外も通知系サービスを揃えて強化しているし。

確認ができた他の三つ(websocket.in, achex, scaledrone)でやってみますかね。

websocket.in はとてもシンプルでクリーンなサイトで初心者向け。ちょっと https://www.websocket.in/test-online ページは説明不足感があるけど簡単という印象。チャンネル名が数字なのはちょっと不便な印象。 無料での 30 messages/min というのは以外と引っかかる可能性あるのでちょっと気になる。

scaledrone も同様にシンプル、オンライン実行ページはないが quick start https://www.scaledrone.com/docs/quick-start/javascript は親切だし、管理画面からのデバッグ機能もあるので便利。 無料では同時接続数 20 と一日 100,000 メッセージという比較的緩い制約。

achex は他の2つに比べると少しごちゃっとしてる印象。アカウントがメールアドレスじゃなくて Google アカウント紐付けなのも少し気になる。

管理画面のデバッグ機能が便利でドキュメントも綺麗、無料プランの充実度という意味では scaledrone が一番初心者向けのデフォルトとして説明してよさそう。

satakagi commented 4 years ago

achex 、何もアカウント登録とかしなくて無造作に使えてしまっている感じがするのですが、何か勘違いしているでしょうか<自分

satakagi commented 4 years ago

https://github.com/chirimen-oh/remote-connection つくりました。

Exampleを置くつもりなので、githubPagesも設定してあります。 http://chirimen.org/remote-connection/

dynamis commented 4 years ago

メッセージのin/outだけということにして、

確かにイベント種別の定義もナシで取りあえず全送信・全受信が一番シンプルですね。

オブジェクト (json string) で送って適当なプロパティでフィルタさせるか、配信対象を本当に絞りたければ複数チャンネルを使うかしてくださいという感じでもアリですからね。

let event-of-channel = await relayServer.subscribe('channel-name', 'event-name');

など出来ても (使わない人は意識しないで済む形に出来て) ありかもですがちょっと実際のチャンネルと対応して無くて気持ち悪すぎるし、今回の目的としては不要ですね。

achex 、何もアカウント登録とかしなくて無造作に使えてしまっている感じがするのですが、何か勘違いしているでしょうか<自分

achex は Achex Cloud と Legacy Server の二つがありました: https://achex.ca/develop/

ws://achex.ca:4010/ に接続する Legacy Server が認証も何もなしでいきなり使えるやつ (password に何を与えても認証が通るw) で、username を to で指定して対象を絞って送受信が可能だから、今回それをチャンネル名と考えて使う形にしたわけですね。

wss://cloud.achex.ca/ に接続する Achex Cloud の方が Google アカウントで登録、各自の Hub や User を登録して使うタイプでした。

登録不要な Legacy 使うと簡単な一方、サンプルコードそのまま使ってデフォルトの username に接続したユーザ同士で混信する罠があり、Achex だけ他と違う感じになるのがちょっと気持ち悪いかも。

Achex Cloud で Hub/User 作って使う形なら他のサービス同様自分達のデバイス同士だけの通信になりますが、Hub/User/Sid の 3 層があって複雑&ドキュメント分かりにくい&パネル画面の挙動が不安定なので Achex Cloud 使うのも初心者向けにならなさそうですから、"Free for everybody. Recommended for beginners and educational purposes." といってる Legacy の方を使う今のモノで良さそうです (チャンネル変更忘れでの混線は仕方ないので、Achex 利用時の注意として明記するくらいか)。

note: 見つけにくい achex テストページ: http://achex.ca/dev/example.html (https にしちゃだめw) https://achex.ca/dev/general_testing.html

dynamis commented 4 years ago

https://github.com/chirimen-oh/remote-connection つくりました。

ざっと見て補足の追加やフォーマットの調整などの編集しました。

https://chirimen.org/remote-connection/polyfill/RelayServer.js

ブラウザ標準 API の未実装の隙間を埋めるものではないので、polyfill とは言わない、単なるライブラリじゃないでしょうか。フォルダ名としては js なり lib なりが一般的?

Example

http://chirimen.org/remote-connection/examples/example1.html にリンクしていますが今のところ examples ディレクトリはコミットされていません。

dynamis commented 4 years ago

良ーく考えてみるといまの実装の API は extension api の runtime に近く、一般の Web 開発者の馴染みがある WebSocket や Window の postMessage 機能いずれとも異なりますね。

どちらかに合わせつつ不足する機能は急がないけど ToDo issue を立てておくのが良さそうです。 専用のリポジトリ remote-connection ができたので詳細はそちらにて。

リモートの例としては PC+microbit <--> PC の例をこちらに追加頂きました: https://chirimen.org/chirimen-micro-bit/examples/

他にもあると望ましいパターン

satakagi commented 4 years ago

このISSUEは、 https://github.com/chirimen-oh/remote-connection/issues にtransferということで、ひとまずは進めますかね

dynamis commented 4 years ago

そうですね。コードがあるところの issue に書くのが良いと思うので、少なくとも API 議論はremote-connection と思いましたが、どんなサンプル作るのかという話も含めて全部 remote-connection の方にまとめると言うことでこちらクローズしましょうか。