YSNHatenaBlog

主にアプリやWebサービス開発について

Firebase AuthのデータをGoogleスプレッドシートに出力する

タイトルの通りですが、Firebase AuthのユーザレコードをGoogleスプレッドシートに直接書き出すnodeプロジェクトを作ってみました。

github.com

直接実行するもよし、Cloud Functionsを含めコンテナを起動させて実行するもよし。特に後者の場合はローカルにダウンロードすることなく、アクセス制限のかかっているGoogle Drive上にアップロードすることができます。スプレッドシートなのでその後でフィルタしたり検索したりも簡単。

Next.jsのgetServerSidePropsで遅くなったので対策を考えてみたが...

Cloud FunctionsでNext.jsを動かしてWebアプリを運用してみている。

クライアントサイドで非同期にパラメータ取得した方が画面遷移が早くて爽快だったのだけど、OGPとかやりたいときに困るので getServerSideProps を入れてみた。 その結果、これを入れたページへの画面遷移が極端に遅くなった。

調べてみたけど、画面遷移で来たか初期ロードかはこんな感じで判定できそう。

if ((context.req as functions.https.Request).originalUrl.endsWith('.json')) {
  return {}
}
// 重い処理
return { /* 処理結果 */ }

ただこの「重い処理」が画面遷移にかかる時間の大半を締めていればいいのだが、自分の場合、クライアントとCloud Functionsとの通信にかかる時間の方がほとんどだった。hostingの制限上、Cloud Functionsはus-centralだし...。

処理も複雑になるし、断念。

追記

getInitialProps を使えばいいのかもしれないけど、パスによる場合分けが必要そうなのと、自動最適化が効かなくなるみたいなのでん〜という感じ。

メタデータを使ってFirebase Storageのデータを特定のユーザ間でシェアする方法

ここのグループ非公開のファイル メタデータを使用する方法。

ユーザーデータを保護する  |  Firebase

1対1で相手に画像を送信したい場合

送信者と受信者のAuth uidがクライアント側で分かる前提。

iOSの場合

let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
metadata.customMetadata = [
    "sender": <送信者のuid>,
    "receiver": <受信者のuid>
]
let storage = Storage.storage()
let ref = storage.reference().child("share/photo.jpg")
ref.putData(data, metadata: metadata)

Storageのルールはこんな感じ。

service firebase.storage {
  match /b/{bucket}/o {
    match /share {
        match /{allPaths=**} {
        allow write: if request.auth.uid != null && request.resource.metadata["sender"] == request.auth.uid
        allow read: if request.auth.uid != null && (resource.metadata["sender"] == request.auth.uid || resource.metadata["receiver"] == request.auth.uid)
      }
    }
  }
}

Firestoreで管理しているグループ内で共有したい場合

Storageのオブジェクト作成時にCloud Functionsを実行してメタデータを付けてあげる。 例えば送信者の属するグループがFirestoreで管理されており、そのグループのメンバーのみで画像を共有したいとする。

iOSアプリ側のコード。

let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
metadata.customMetadata = [
    "sender": <送信者のuid>
]
let storage = Storage.storage()
let ref = storage.reference().child("share/photo.jpg")
ref.putData(data, metadata: metadata)

Cloud Functionsのコード。

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';

admin.initializeApp();

export const storagePermission = functions.storage.bucket().object().onFinalize(async (object, context) => {

    // <必要に応じてパスのチェックなどをする>

    const sender = object.metadata && object.metadata['sender'];

    if (sender && object.name) {

        const uidsInGroup = ... // Firestoreから共有したいユーザのuid配列を取得

        const file = admin.storage().bucket(object.bucket).file(object.name);
        await file.setMetadata({
            metadata: {
                uids: uidsInGroup.join(',')
            }
        }) // カスタムメタデータをセットする際setMetadataの引数オブジェクトにmetadataのキーが必要。
    }
    return
}

Storageのルール。

service firebase.storage {
  match /b/{bucket}/o {
    match /share {
        match /{allPaths=**} {
        allow write: if request.auth.uid != null && request.resource.metadata["sender"] == request.auth.uid && resource.metadata["uids"] == null
        allow read: if request.auth.uid != null && resource.metadata["uids"] != null && request.auth.uid in resource.metadata["uids"].split(",")
      }
    }
  }
}

メタデータにひたすら <uid>: "true" をセットする方法もありそうだが散らかりそう。

追記

このルールだと他のユーザが上げたデータ上書きできちゃうからStorageのパスは考えた方がいいかも。

SwiftのOptionalのオーバーヘッド

気になることがあってOptionalのunwrapのオーバーヘッドを調べてみました。

先に結論

  • 参照型のunwrapはパフォーマンスが低下する
  • 値型のunwrapはあまりパフォーマンスは低下しない

方法

Counterという型のcountを10,000,000回インクリメントするのを10回平均するコードを書いて、 アプリの起動時に計測させるようにしました。

環境: iPhone 7, デバッグ実行

Optionalな参照型の場合

class Counter {
    
    var count: Int = 0
}

let n = 10
var sum: TimeInterval = 0

for _ in 0..<n {

    let now = Date()
    let counter: Counter? = Counter()

    for _ in 0..<10000000 {
        
        counter?.count += 1
    }
    
    sum += -now.timeIntervalSinceNow
}

let ave = sum / Double(n)
print(ave)

結果: 0.248754590749741[秒]

counterのOptionalを外してみました。 該当箇所だけ書きます。

    let counter: Counter = Counter()

    for _ in 0..<10000000 {
        
        counter.count += 1
    }

結果: 0.0492702841758728[秒]

かなり違います。

ちなみにforce unwrapでも遅いです。

let counter: Counter! = Counter()

for _ in 0..<10000000 {
    
    counter.count += 1
}

結果: 0.234673696756363[秒]

Optionalな値型の場合

ほぼ同じコードで、Counterstructにしてみます。

struct Counter {
    
    var count: Int = 0
}

let n = 10
var sum: TimeInterval = 0

for _ in 0..<n {

    let now = Date()
    var counter: Counter? = Counter()

    for _ in 0..<10000000 {
        
        counter?.count += 1
    }
    
    sum += -now.timeIntervalSinceNow
}

let ave = sum / Double(n)
print(ave)

結果: 0.0521551012992859[s]

速いです。

Optionalを外してみます。

var counter: Counter = Counter()

for _ in 0..<10000000 {
    
    counter.count += 1
}

結果: 0.0496872007846832[s]

あまり変わらないです。

irMagician + Raspberry Pi + HomeKitで家電を遠隔&音声制御する

音声でテレビを操作したい、帰宅前にエアコンをつけたい、といったことは誰でも考えたことがあると思います。 今持っている普通の家電でこれを実現したいと思ったときに、

  • 音声認識して家電操作コマンドに変換して赤外線を飛ばすようなソフトとハードを作る。
  • 家に近づいたことを検知するためのアプリを作る。
  • 宅外から宅内のネットワークにある機器を操作するためのシステムを用意する。

など、いざ実現しようとすると意外と難易度が高いことに気づきます。

しかし今回、HomeKitを使った機器連携でこれらが簡単に実現することができました。 今回の記事では、その方法についてまとめたいと思います。

HomeKit

iOS10からホームアプリが追加され、HomeKit対応デバイスをこのアプリから管理できるようになりました。

f:id:yosuke403:20170107185438p:plain

ホームアプリにHomeKIt対応デバイスが登録されるとiOSから操作が可能になるのですが、 単純な操作だけでなく、次のようなことが可能になります。

1. iOSのコントロールセンターから操作できる。

f:id:yosuke403:20170107211712p:plain

2. Siriから操作できる。
3. Apple Watchから操作できる。

f:id:yosuke403:20170107211254p:plain

4. 家族のIDを登録すれば、家族が自分のiOSバイスから操作できる。
5. Apple TVを持っていれば、宅外から操作したり、位置情報やタイマーをトリガーに操作できる。

f:id:yosuke403:20170107211549p:plain f:id:yosuke403:20170107211627p:plain

便利ですね!

つまりHomeKItに対応したデバイスは、音声操作したり宅外操作したりできるわけです。

HomeKit APIをエミュレートするhomebridge

homebridgeはHomeKit APIをエミュレートするNodeJSサーバです。 起動すると、HomeKit対応デバイスとして、そのサーバをホームアプリに登録できるようになります。

nfarina/homebridge: HomeKit support for the impatient

ホームアプリから操作した際の動作は、homebridgeプラグインによってカスタマイズできます。 homebridge-cmdプラグインを使用すると ホームアプリの操作に対応して、任意のコマンドを実行できます。

「irMagician + Raspberry Pi + homebridge」で家電を遠隔操作

irMagicianは大宮技研さんが販売している赤外線リモコンシステムです。 低価格かつ軽量でコマンドラインからでも制御できるのが特徴です。

irMagician – 高機能/低価格赤外線リモコン | 大宮技研 合同会社

irMagicianとRaspberry Piの組み合わせは、HomeKit経由で赤外線リモコンを操作するためのミニマム構成です。 先に説明したhomebridge-cmdでirMagicianを操作して赤外線を自由に発射できます。 フレキシブル延長ケーブルを使うと、向けたい方向にirMagicianを固定できて便利です。

Amazon | エスエスエーサービス [ USB A(オス) - USB A(メス) ] フレキシブル延長ケーブル SU2-AA03FB | エスエスエーサービス | USBケーブル通販

f:id:yosuke403:20170103230320j:plain

見ての通り、irMagicianを使うと全体の構成はコンパクトで済み、かつ低価格なのでオススメです。

全体構成

今回の、宅外からApple TVを介して宅内の機器を操作する際の構成図です。

f:id:yosuke403:20170103224200p:plain

iOSバイスが宅内のWi-Fiに繋がっている場合は、iOSバイスが直接Raspberry Piにアクセスすることになります。

設定方法

irMagician + Raspberry Pi + homebridgeの設定についてはこちらの記事が参考になりました。

iOS10「ホーム」アプリと「Raspberry Pi + irMagician」でお手軽家電操作。 - Qiita

この記事ではSiriについてや、その他ハマったところを書きたいと思います。

Siriで操作するには?

ホームアプリにRaspberry Piを登録するときに名前をつける(例えば「暖房」など)と、その名前で反応するようになります。 Siriを起動し、「暖房の電源を入れて。」と命令すれば電源が入ります。

f:id:yosuke403:20170107220003p:plain

ただし、「エアコン」という名前にして「エアコンの電源を入れて。」と命令しても「エアコンは見つかりませんでした」と返されてしまいます。 おそらく、Siriではアクセサリのカテゴリ(「照明」や「エアコン」など)を指定するだけで、 そのカテゴリに対応するアクセサリが登録されていれば、そのデバイスを操作しようとするのだと思います。

この辺りにリストアップされているのが、カテゴリの名前になると思われます。 www.apple.com Accessory Category Types - HomeKit | Apple Developer Documentation

その他ハマったところ

homebridgeが見つからない

usernameを変更してみてください。 MACアドレスのように見えますが、Raspberry PiMACアドレスと合わせる必要はないようです。

{
    "bridge": {
        "name": "YourBridgeName",
        "username": "CC:22:3D:E3:CE:50", # <- ここを変更
        "port": 51826,
        "pin": "031-45-403"
    },
    # 省略
}
ログインしたのにApple TVと連携されない

f:id:yosuke403:20170104233550j:plain

Apple TVにログインしたのですが、iPhoneを4G回線に切り替えても最初は宅内の機器が見えませんでした。 いろいろ調べて、設定確認したり、ログインし直したり、再起動したりしてみたのですが結局ダメで、 一旦あきらめて次の日おもむろに起動したところ成功しました!

設定の同期タイミングの問題だったのでしょうか...。

動作の様子

実際の動作の様子です。

前半は宅外から操作するデモ(4G回線でつなげています)、後半はSiriで操作するデモになっています。

www.youtube.com

※ 使用しているエアコンはPanasonicのCS-264CF2です。 http://panasonic.jp/aircon/housing/p-db/CS-364CF2S.html

さいごに

HomeKit、思った以上に便利でした。Raspberry PiとirMagicianも使うことで夢が広がります。

一方、公式にHomeKit対応しているデバイスは少ない状況のようです。

HomeKit - すべてのアクセサリ - Apple(日本)

早くいろんな家電に対応してもらいたいですね。

おうちハックに挑戦してコンテストまで参加できた話

今回、IoT的なことに挑戦してみたので、ブログに書いておこうと思います。

生活デザインコンテスト出品

今回、大阪で行われた「生活デザインコンテスト」に応募し、出品させていただくことができました。

生活デザインコンテスト#3 – 生活デザインプロジェクト

作品名は『家族の「ただいま」を教えてくれるキーホルダー』というものです。

家族の「ただいま」を教えてくれるキーホルダー | ナレッジコネクター + Idea.LinkData

youtu.be

これはQUALY社のSparrow Keyringという鳥の巣箱型キーホルダーを使ったもので、鍵を巣箱に戻すと「ただいま」、巣箱から出すと「行ってきます」を家族のスマートフォンに通知するというデモになっています。

いろいろ課題があって実際の運用まではできていないのですが、なんとかコンセプトを伝えられるところまで実装できました。

作品出品までの道のり

おうちハック発表会のLTに挑戦

コンテスト応募のきっかけは「おうちハック発表会」での発表でした。

このイベントを開催しているおうちハック同好会には、知り合いが多く参加しており、自分も発表したいと思い今回の作品を作りました。

自分はアプリ開発ならできるのですが組み込みは初心者で、とにかくRasbperry PiやArduino、それらに関する書籍を買って、何ができるのか、どうやったらできるのかをとにかく調べました。

最初のネタとして、Netatmoを使った、屋外の気温を朝通知してくれるアプリを作りました。Netatmoは組み込み知識が不要ですが、簡単にIoTを体感できる面白いデバイスです。しかしこの気温アプリ、妻に全然使ってもらえませんでした。朝携帯を全く見ない妻には、このアプリは意味がなかったようです。その代わりのネタを探したところ、先に述べたキーホルダーが面白そうだったので、鍵の検知をする方法を知り合いに聞いたりして作ってみました。

おうちハック発表会では、このときの経験した「家族に使ってもらうって難しい!」という題材で発表しました。やはり家族に自分の作品を使ってもらっている人は少なく、共感を得ることができたかなと思いました。またスライドで課題を共有したところ、会の後に解決策をいろんな方に考えていただきました。

www.slideshare.net

生活デザインコンテストへの応募

おうちハック発表会で、ある程度動くものとして作り上げられたことと、見た目の可愛さを評価していただき、生活コンテストへの出品を提案していただきました。

ポスターや作品資料など、いろいろ作成する必要があるのですが、これが結構大変でした。とりあえずデモとして動けばいいとガチャガチャ作っていたこともあり、作品資料とするにはもう一度自分のやったことを整理する必要がありました。特にプログラムやAWSなどについては、別件で持っていた有物を使って動かしていたところを解きほぐして、無駄な処理や構成を省いて精錬していきました。

資料はこちらに上がっています。一応初心者でも再現できるよう、頑張って書いています。

インターネットアカデミーでの発表会

開催後には、インターネットアカデミーにて発表させていただく機会をいただきました。

おうちでできるIoTを体験しよう!『生活デザインコンテスト 優秀作品発表会』8/27開催 - インターネット・アカデミー

実は生活デザインコンテストでは、ネットワークがなかったこともあり動くものを展示できなかったので、今回初めての人前でのデモになります。そこで、課題だった感圧センサーの低い認識率を、おうちハック発表会でいただいたRFIDのアイデアで解決してみたところ、デモとしてちゃんと動かすことができました。もう少し改良したらまた発表したいです。

発表した結果

おうちハック発表会での発表をきっかけに、いろんな経験ができ、またいろんな方と繋がることができました。 発表会で会った方が開催する、別のIoTの勉強会に呼んでいただき、そこでまたいろんな人の話を聞くことができました。 またインターネットアカデミーの発表会でも、コンテストに参加した方の素晴らしい作品を体験したり、また作者の人の話を聞くことができました。

さらに、「キーホルダーの人ですか!」「あのスライドみました!」と言ってもらえたのがすごい嬉しかったです。自分がIoTの世界に一歩踏み込めた気がしました。

まだまだこの作品は改良の余地があるので、引き続き取り組んでいこうと思います。

IoT縛りの勉強会! IoTLT vol.13に参加してきました

IoTLT初参加

遅くなりましたが、3/16(水)にIoTLTに参加してました。 印象に残った発表のみピックアップして感想を書きまっした。

発表

Node.js x IoT

@yosuke_furukawaさん。Node.jsユーザグループの代表。

IoT周りのNodeの話でした。

Nodeはビルド済みのバイナリが配布されており、ビルドする必要がないのが特徴。ARM用のバイナリ(Node and ARM)も配布しているそうです。

IoT関係のライブラリとして「Johnny-Five」が紹介されていました。 rwaldron/johnny-five: JavaScript Robotics and IoT programming framework, developed at Bocoup.

Javascript Robotics programming framework。Arduino, Tessel 2, BeagleBone, Intel Edison, Particle Photonなどいろんなボードに使えるみたいです。 例えばこんなコードでLチカできます。ArduinoC/C++ベースのプログラミングだった気がするのですが、JSで書けるのはWeb系のエンジニアにはとても嬉しいですね。

var five = require("johnny-five");
var board = new five.Board();

board.on("ready", function() {
  var led = new five.Led(13);
  led.blink(500);
});

(もう一つあったけどメモしそびれました...)

このJohnny-Fiveについて、チュートリアル形式で学べる「nodebot-workshop」と言うnpmがあります。

チュートリアルをnpmで配るっていうのがすごいですね。ただしCLIで行うため結構つらいとのこと。

また、V8に変わる実行エンジンとしてMicrosoftのchakracore-node が登場しているとのこと。Windows上で動作する省エネ重視のエンジンらしいです。 nodejs/node-chakracore: Node.js on ChakraCore https://github.com/nodejs/node-chakracore

IoTで家庭の問題を解決したい話

@yamacho1111 イノベーター・ジャパン

speakerdeck.com

SONYのMESHを使って、鍵の開閉を記録してみた話でした。 MESH:遊び心を形にできる、アプリとつなげるブロック形状の電子タグ|ソニー

目的は奥様に家の鍵を閉めさせることだそうです(笑)。MESHで鍵の開閉を検知して、IFTTT経由でGoogle Docsに記録していく仕組みです。

鍵が閉まっていなかったらHueで知らせるようにしたところ効果があったとのこと。家の中で携帯を見させるというのは難しいので、他の手段で気づかせるという仕掛けは重要な気がしています。 [IoTで家庭内の問題を解決したい話

MESHはそこそこ値段はするようですが、機能ごとにバラ売りしています。何か解決したい課題にはまりそうなら購入してみたいですね。

IoTをインターネット越しに見る

@tnohoさん

www.slideshare.net

個人的にいちばん共感した話。IoTで機器をリモートコントロールする話はよくありますが、実際ちゃんと動いたか、何か問題は発生していないか(特に火事とか)を見られるように現場の映像も必要になるだろうという話。この映像転送のためにWebRTCはどうかという提案です。

動画配信サービスを自分で作るのは、たとえAWSのようなサービスを利用しても結構大変だと思いますが、WebRTCを使うと簡単に実現できます。P2Pで通信することも可能なので、バックエンドの運用コストも少なくて済むはず。何か使いやすいキットがあれば流行りそうですね。

終わりに

IoT関係の勉強会は初参加でしたが、白愛話がいろいろ聞けてよかったです!