Google の Firebase には Realtime Database というものがあって、複数のアプリケーション(デスクトップPC、スマホ、M5Stack などなど)を使って同期ができます。って記事が、あちこちにあるので、ならば、Scratch でもできるのでは?と思って作ってみたのがこれ。
1画面にはなっていますが、デスクトップPCからノートPCにリモートデスクトップ表示しているところです。別のPCでスクラッチを起動しておいて(ひとつのPCでは複数起動できないので)、片方のオレンジ猫を動かすと、もうひとつのオレンジ猫が動きます。
直接通信させることもできるのですが、Google の Firebase を経由させます。
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 形式でごっそりとデータを置きます。
こんな風に、/scratch/firecat というパス(フォルダーのようなものか)の下に、データが置かれます。謎な文字は識別子みたいなものですね。プロパティとして、From, To, Text, X, Y を置くためには、下記のようなクラスを作っておきます。
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
C# だとこんな感じ
1 2 3 4 5 6 7 8 | 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 ; } } |
このデータクラスを、Firebase に対してアップロードします。F# で作る場合は、こんな風に、各種の関数を作っておくと便利です。詳しい中身は先の記事の C# コードを読んだほうがよいでしょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | // ログイン 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 -> () | _ -> () ) () |
これを、Scratch から呼び出せるように HTTP サーバーを作っておきます。スクラッチからは、/say/me/you/hello のようなスラッシュで区切られてデータが送られてくるので、これをパースして、Firebase に保存します。
もうひとつの PC では、リアルタイム監視(startWatchingRealtimeで登録)をしているので、これを fireData に保存しておいて、スクラッチのポーリング(/poll)に送られるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | // 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() () |
スクラッチから送られてくるコマンド(say, sayall, movex など)は自分で定義をします。スクラッチのメニューでシフトキーを押しながら「ファイル」を選択すると「実験的なHTTP拡張の読み込み」が出てくるので、ここで作成した firescratch.json を読み込ませます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | { "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" ] } } |
サーバーを立ち上げて、コマンド待ち状態になると「その他」のところがグリーンになります。
これを2つのPCで起動させて、スクラッチ同士で通信させたのが、https://twitter.com/moonmile/status/1044440471823478784 にある動画になります。
相互通信にしたいところだけど、ひとまず一方向だけのスクリプトを
送信元のスクリプト
送信先のスクリプト