Firebaseを使い、Scratchで相互通信する

Google の Firebase には Realtime Database というものがあって、複数のアプリケーション(デスクトップPC、スマホ、M5Stack などなど)を使って同期ができます。って記事が、あちこちにあるので、ならば、Scratch でもできるのでは?と思って作ってみたのがこれ。

image

1画面にはなっていますが、デスクトップPCからノートPCにリモートデスクトップ表示しているところです。別のPCでスクラッチを起動しておいて(ひとつのPCでは複数起動できないので)、片方のオレンジ猫を動かすと、もうひとつのオレンジ猫が動きます。

直接通信させることもできるのですが、Google の Firebase を経由させます。

image

Scratch なところは、デスクトップアプリ(WPFとか)でもよいし、スマホアプリを Xamarin で作ることもできます。便利かどうか別として、まあ、こんなことができるということで。

Firebase の基本的なところは、

C#でFirebaseを使ってみよう!(1) FirebaseとEmail-Password認証 – こっちみないで(´・ω・`) http://kmycode.hatenablog.jp/entry/2017/02/09/205655

Firebase Realtime Database のデータ保存、取得、ストリーミング受信実験( ESP32 , M5Stack ) | mgo-tec電子工作
https://www.mgo-tec.com/blog-entry-firebase-realtime-database-sever-sent-events-esp32-m5stack.html

なところを参考にしています。Firebase は API KEY を取得してアクセスするのですが、そのままだと誰でもアクセスできてしまうので、一応ユーザー名(メールアドレス)とパスワードでガードを掛けます。

C# や F# から Firebase を扱うときは、NuGet で次の2つを追加します。

  • FirebaseAuthentication.net
  • FirebaseDatabase.net

内部で Rx が使われているらしく、System.Reactive や System.Reactive.Linq などが同時にインストールされます。

先の記事では、C# でサンプルが書かれているのですが、FireScratch の場合は都合上 F# で書いています。まあ、以前作った NetScrattino が F# だったので、それを踏襲したかっただけなんですけどね。

Firebase 自体は NoSql なので、JSON 形式でごっそりとデータを置きます。

image

こんな風に、/scratch/firecat というパス(フォルダーのようなものか)の下に、データが置かれます。謎な文字は識別子みたいなものですね。プロパティとして、From, To, Text, X, Y を置くためには、下記のようなクラスを作っておきます。

[code lang=”csharp”]
type Data() =
let mutable _from : string = ""
let mutable _to : string = ""
let mutable _x : int = 0
let mutable _y : int = 0
let mutable _text = ""

member x.From with get() = _from and set(v) = _from &<- v
member x.To with get() = _to and set(v) = _to <- v
member x.X with get() = _x and set(v) = _x <- v
member x.Y with get() = _y and set(v) = _y <- v
member x.Text with get() = _text and set(v) = _text <- v
[/code]

C# だとこんな感じ

[code lang=”csharp”]
public class Data
{
public string Text { get; set; }
public string From { get; set; }
public string To { get; set; }
public int X { get; set; }
public int Y { get; set; }
}
[/code]

このデータクラスを、Firebase に対してアップロードします。F# で作る場合は、こんな風に、各種の関数を作っておくと便利です。詳しい中身は先の記事の C# コードを読んだほうがよいでしょう。

[code lang=”csharp”]
// ログイン
let singIn() =
let auth = new FirebaseAuthProvider( new FirebaseConfig( apikey ))
authLink <- auth.SignInWithEmailAndPasswordAsync( email, passwd ).Result
printfn "サインインに成功しました"

// クエリを取得
let GetDatabaseQuery( path ) =
let opt = new FirebaseOptions()
opt.AuthTokenAsyncFactory <- fun () -> Task.FromResult( authLink.FirebaseToken )
let client = new FirebaseClient( databaseURL, opt )
client.Child( path )

// データをアップロード
let upload( data : Data ) =
let query = GetDatabaseQuery( DatabasePath )
query.PostAsync( data ) |> ignore
()
// テキストを保存
let uploadText( text: string ) = upload( new Data( Text = text ))

// テキストを取得
let downloadText() =
let query = GetDatabaseQuery( DatabasePath )
let results = query.OnceAsync<Data>().Result
let items = results.Select( fun o -> o.Object )
items.First().Text

// リアルタイムデータの監視
let startWatchingRealtime() =
realtimeDatabaseWatcher <-
GetDatabaseQuery(DatabasePath)
.AsObservable<Data>()
.Subscribe( fun ev ->
if ev <> null then
let text = ev.Object.Text
match ev.EventType with
| FirebaseEventType.InsertOrUpdate -> fireData <- ev.Object | FirebaseEventType.Delete -> ()
| _ -> ()
)
()
[/code]

これを、Scratch から呼び出せるように HTTP サーバーを作っておきます。スクラッチからは、/say/me/you/hello のようなスラッシュで区切られてデータが送られてくるので、これをパースして、Firebase に保存します。

もうひとつの PC では、リアルタイム監視(startWatchingRealtimeで登録)をしているので、これを fireData に保存しておいて、スクラッチのポーリング(/poll)に送られるようにします。

[code lang=”csharp”]
// Scratchから受信するためのHTTPサーバー
let Server( port ) =

// firebase にログイン
singIn()
startWatchingRealtime()

let listener = new System.Net.HttpListener()
listener.Prefixes.Add("http://127.0.0.1:"+(port |> string)+"/" )
listener.Start()
while true do
let context = listener.GetContext()
let res = context.Response
let mutable data = ""
let path = context.Request.Url.PathAndQuery
match path with
| "/poll" ->
data <- data + String.Format("text {0}\n", fireData.Text )
data <- data + String.Format("from {0}\n", fireData.From )
data <- data + String.Format("to {0}\n", fireData.To )
data <- data + String.Format("x {0}\n", fireData.X )
data <- data + String.Format("y {0}\n", fireData.Y ) // printfn "%s" path // printfn "%s" debug | "/reset_all" ->
printfn "/reset_all"
data <- "ok" | _ ->
let pa = path.Split([|’/’|])
match pa.[1] with
| "say" ->
let me = pa.[2]
let you = pa.[3]
let text = pa.[4]
printfn "say %s %s %s" me you text
upload( Data( From = me, To = you, Text = text ))
| "sayall" ->
let text = pa.[2]
printfn "sayall %s" text
uploadText( text )
| "movex" ->
let me = pa.[2]
let x = pa.[3] |> int
printfn "movex %s %d" me x
upload( Data( From = me, X = x ))
| "movey" ->
let me = pa.[2]
let y = pa.[3] |> int
printfn "movey %s %d" me y
upload( Data( From = me, Y = y ))
| "movexy" ->
let me = pa.[2]
let x = pa.[3] |> int
let y = pa.[4] |> int
printfn "movexy %s %d %d" me x y
upload( Data( From = me, X = x, Y = y ))
| _ ->
data <- ""
printfn "%s" path
res.StatusCode <- 200
let sw = new System.IO.StreamWriter( res.OutputStream )
sw.Write( data )
sw.Close()
()
[/code]

スクラッチから送られてくるコマンド(say, sayall, movex など)は自分で定義をします。スクラッチのメニューでシフトキーを押しながら「ファイル」を選択すると「実験的なHTTP拡張の読み込み」が出てくるので、ここで作成した firescratch.json を読み込ませます。

image

[code lang=”csharp”]
{
"extensionName": "Firebase Scratch",
"extensionPort": 5411,
"url": "https://github.com/moonmile/firescratch",
"blockSpecs": [
[ " ", "%s と言う", "sayall", "hello world." ],
[ " ", "%s が %s さんへ %s と言う", "say", "me", "you", "hello" ],
[ " ", "%s を X座標 %d にする", "movex", "me", 0 ],
[ " ", "%s を Y座標 %d にする", "movey", "me", 0 ],
[ " ", "%s を X座標 %d、 Y座標 %d にする", "movexy", "me", 0, 0 ],

[ "-" ],
[ "r", "自分", "from" ],
[ "r", "相手", "to" ],
[ "r", "X座標", "x" ],
[ "r", "Y座標", "y" ],
[ "r", "メッセージ", "text" ],
[ "-" ]
],
"menus": {
"OnOffValues": ["ON", "OFF"]
}
}
[/code]

サーバーを立ち上げて、コマンド待ち状態になると「その他」のところがグリーンになります。

image

これを2つのPCで起動させて、スクラッチ同士で通信させたのが、https://twitter.com/moonmile/status/1044440471823478784 にある動画になります。

相互通信にしたいところだけど、ひとまず一方向だけのスクリプトを

送信元のスクリプト

送信先のスクリプト

サンプルコード

https://github.com/moonmile/FireScratch

カテゴリー: Scratch パーマリンク