みずぴー日記

人間の再起動ボタンはハワイのビーチにある

🇹🇼台湾

勤労感謝の日による3連休に台湾に行った。 勤労に感謝している。

f:id:mzp:20181129224749p:plain

👣観光

九份

九份を見にいった。これが一番の目的。

雨がふっててつらかった。

f:id:mzp:20181123143140j:plain

途中の道がせまくて歩きづらかった。

f:id:mzp:20181129223714p:plain

AppleStore台北

台北のAppleStoreの様子を見にいった。

f:id:mzp:20181123123151j:plain

台北101

AppleStoreは台北101という高層ビルにはいってるので、展望台も見にいった。

雨が降ってる日だったので、ほぼ白みないな景観だった。わりといい値段はらったのに...。

f:id:mzp:20181123103015j:plain

🎯看板

看板が漢字でなんとなく読めるが、ちょっと違う。

明朝体の電光掲示板、あまり見ない気がする。書体もちょっと違う気がする。

f:id:mzp:20181123093249j:plain

Exitと出口のタイポグラフィが美しい。

f:id:mzp:20181123094204j:plain

東京最強らしい。

f:id:mzp:20181124094235j:plain

🍜食事

夜市

夜市に行ってみたかったので行った。

f:id:mzp:20181122224329j:plain

が、雨がふってたので、普通の店にはいった。台湾料理セットみたいなもの。

f:id:mzp:20181122191816j:plain

そのあとは他の店でワンタン麺を食べた。おいしい。

f:id:mzp:20181122222649j:plain

さらに、豆花という豆乳プリンを豆乳につけたデザートも食べた。うまい。

f:id:mzp:20181122195259j:plain

小籠包

鼎泰豐にいって小籠包を食べた。

f:id:mzp:20181123114206j:plain

カニミソ小籠包を頼んだら、カニの形にきった皮がはいっててかわいかった。

f:id:mzp:20181123115143j:plain

いっしょに頼んだチャーハンもおいしい。

f:id:mzp:20181123114635j:plain

タピオカミルクティー

タピオカミルクティーも飲んだ。 なぜかビールの容器にはいってる。

f:id:mzp:20181123104016j:plain

しょうがないので、そのあとビールも飲んだ。

f:id:mzp:20181123114046j:plain

18才以上は飲酒可能らしく異文化を感じた。

f:id:mzp:20181123104813j:plain

火鍋

火鍋を食べにいった。からいけどおいしい。

f:id:mzp:20181123195422j:plain

具の半分が以上が何か分からないので、明るい闇鍋みたいになっている。食べてもなにか分からなかった。

f:id:mzp:20181123194024j:plain

📱eSIM

iPhoneXSにしたので、GigSkyを契約して使っていた。

現地でプリペイドSIMを買うのと比べて、抜いたSIMカードをどこにしまうかを考えなくていいのが楽。

f:id:mzp:20181122164121j:plain

🚃移動

移動はMTR(地下鉄)とUberを使った。

MTRは券売機で一回切りのトークンが買える。 改札機にタッチすると通れる。

f:id:mzp:20181123091557j:plain

Uberはいつも通りの利用感で安心できる。割り勘機能がうまく使えなかった気がする。

その他

ANAのPP修行は失敗しました。(5万ポイントを貯めるつもりだった)

💨呼吸時間の記録

Apple Watchには「呼吸」アプリがある。深呼吸によるリラックスを支援をするアプリである。(参考: 呼吸 App を使う)

呼吸した時間をPixelaに記録し始めた。

f:id:mzp:20181125120710p:plain

🎯一覧性と継続のモチベーション

呼吸した時間はヘルスケアアプリに記録される。 しかし、継続日数が読み取りづらく、毎日続けるモチベーションになりづらい。

✨Pixela

Pixelaは任意のデータをGitHubのコントリビューショングラフ(通称: 草)にするサービスである。

f:id:mzp:20181125121617p:plain

commit以外の数値でも草を生やせる、PixelaというAPIサービスを作った! - えいのうにっき

⏫ショートカットを用いた呼吸時間の記録

ショートカット(旧: WorkFlow)で呼吸時間をPixelaに記録する。

ショートカットを不特定多数に公開する方法はないので、画像を貼っておく。

(オリジナルサイズで表示)

おもに次の処理を行なっている。

  1. ヘルスケアから呼吸時間を取得する
  2. 合計時間を計算する
  3. Pixelaに記録する
  4. 1〜3を過去n日分に繰替えす

最初は作成したのち更新する、という流れにしたが、更新のみでできるように pixe.la の仕様が変更された。(最高!!)

💓所感

iPhoneアプリを書かずにiPhoneの情報を扱えるショートカットは便利。ただ版管理機能や共有機能が弱いのは厳しい。

Pixelaは手軽でいい。見慣れた見た目になる、簡単なAPIで使えるので使い出がありそう。ただ、データを確認するときに、いちいちcurlを叩くのは大変だったのでPawを使った。

f:id:mzp:20181125123625p:plain

🔐二要素認証

Twitterなどの二要素認証では、二要素認証用のアプリで生成した認証コードを使う。

認証コードをどのように生成しているのか、Twitterとは無関係のアプリで生成した認証コードが利用できるのか、クラウドで同期できるのかが疑問だったので実装した。

🔍調査

なにから調べていいか分からなかったので、普段使っているAuthyのサイトを見ていたら、二要素認証の種類について説明しているページがあった。

f:id:mzp:20181103234308p:plain Authy features

これによると「TOTP(Time-based One-Time Password)」という方式らしい。

あの6桁の数字はワンタイムパスワードなのかと気づいた。

📕 TOTPアルゴリズム

TOTPというキーワードをもとに検索すると、RFCまで辿りつく。

TOTPは以下の式で計算される。

 \displaystyle
\mathrm{TOTP}(K) = \mathrm{HOTP}(K, C_T)

時刻T(Unix秒)におけるC_Tは以下の式であたえられる。 T_xワンタイムパスワードが有効な秒数であり、通常は30である。

 \displaystyle
C_T = \lfloor \frac{T}{T_x} \rfloor

Swiftで書くとこのようになる。

public final class TOTPGenerator {
    private let hotp: HOTPGenerator

    public init(secret : [UInt8]) {
        self.hotp = HOTPGenerator(secret: secret)
    }

    public func generate(at date : Date, format: OTPFormat) -> String? {
        let count = UInt64(date.timeIntervalSince1970) / 30
        return hotp.generate(at: count, format: format)
    }
}

📒HOTPアルゴリズム

TOTPはHOTP(HMAC-based One-time Password)のパラメータを時刻にしたものなので、メインのアルゴリズムはHOTPで定義される。

 \displaystyle
\mathrm{HOTP}(K, C) = \mathrm{Truncate}(\mathrm{HMAC-SHA1}(K, C))

Truncateはハッシュ値を指定の桁までに切りつめる操作だが、数式で書くとつらいのでSwiftで書くとこのようになる。 ほぼこのままの式がRFCにのっている。

func truncate(hash : [UInt8]) -> Int {
    let offset = Int(hash[19] & 0x0f)
    return Int(hash[offset] & 0x7f) << 24
                | Int(hash[offset + 1]) << 16
                | Int(hash[offset + 2]) << 8
                | Int(hash[offset + 3])
}

これを使いうとHOTPは以下のコードで生成できる。

import class CryptoSwift.HMAC

class HOTPGenerator {
    private let hmac: HMAC

    init(secret : [UInt8]) {
        self.hmac = HMAC(key: secret, variant: .sha1)
    }

    func generate(at count : UInt64, format: OTPFormat) -> String? {
        guard let hash = try? hmac.authenticate(Array(uint64: count)) else {
            return nil
        }
        // 先頭6文字のみを使う
        return String(String(truncate(code)).suffix(6))
    }
}

🤝秘密鍵の交換

TOTP/HOTPでは秘密鍵を共有する必要がある。

秘密鍵はBase32でエンコードされてやりとりされる。 二要素認証の設定画面に表示される「7nx ofic ...」がそれにあたる。QRコードにも同様の内容が含まれている。

f:id:mzp:20181104001820p:plain

🛠サンプルアプリ

アルゴリズムだけ調べても正しいか不安なので、Swiftでコマンドラインツールを作った。

http://github.com/mzp/totp

Mastodonの二要素認証に表示されるSecret keyを入力して、二要素認証に使えることを確認した。

f:id:mzp:20181103233300p:plain

Swift Package Managerを使ったが、プレインテキストでプロジェクトの構成を管理できてよかった。

👀参考にしたサイト

💕感想

普段、使ってるものの仕組みをちゃんと調べるのはたのしい。

🖥43インチモニタ

43インチの4Kモニタを購入した。 体験がいい。

f:id:mzp:20181021214220p:plain

🛒購入したもの

🍖焼き肉寿司

@izmさんに誘われて焼き肉寿司を食べにいった。

f:id:mzp:20181021222449p:plain

うまいうまいと食べてたら、テンションがあがって4Kモニタを注文してしまった。 もともと欲しいと思ってたので、「ボク使っていますが最高ですよ」と言われて背中を押されてしまった。

@izmさんはiPhoneXSを買っていた。

📦設置

数日後に届いた。 「二人以上で作業しろ」と書いてあった。そんなこと言われても困る。

そこまで重くなかったので、見なかったことにして一人で取り出した。

机に置けないかなと思って挑戦したが、まったく置ける気配がなかった。さらにドライバーがなかったので台の取り付けもできなかった。

とりあえず壁に立て掛けて、MacBookProをつないだ。Netflix言の葉の庭を再生したら最高になった。

🔧モニタアーム

最高だけど床に置いたままにはできないのでモニタアームを注文した。ドライバーのセットも買った。

到着したあと説明書を見ながらアダプタを取り付けたり、モニタアームをデスクに装着したりした。2回くらい取り付け方向をミスったがなんとかなった。

f:id:mzp:20181021215811p:plain

怖かったので、ベッドの位置をずらして、万が一落下しても頭にあたらないようにした。

🔌HDMI変換アダプタ

使ってたHDMI変換アダプタが4K解像度に対応しておらずフルHD解像度しかでなかった。

フルHDはつらいので変換アダプタを注文した。コンセントまで電源ケーブルが届かなかったのでOAタップも買った。あわせ買い対象商品のため、金額調整のために洗濯用洗剤も買った。

これで4K解像度で出力できるようになったので、最高になった。

f:id:mzp:20181021222337p:plain

ばしばしウインドウ開いても狭くならないので最高。若干もてあましている。

f:id:mzp:20181021221437p:plain

🗑今後の課題

💡スマート家電

引越ししたので家電をいくつか進捗した。 せっかくなので、リモートで制御できるものを買った。

🤖ルンバ

床がちらかる前にルンバを買った。iPhoneアプリで制御できるなかでは一番安いモデルにした。

動かしたらいきなりケーブルを巻き込んで停止した。ケーブルボックスを導入したら、なんとかなった。

いまも週一くらいで停止している。かわいい。

💨エアコン

エアコンは部屋についてたので、NatureRemo miniを購入した。

あまり調べずに買ったが、温度調整のUIが格好よくていい。温度計がついてるのもおもしろい。

f:id:mzp:20181010071151j:plain

💡照明

照明も部屋に備え付けだったが、調べたらリモコン操作に対応していた。

リモコンの信号をNatureRemo miniに覚えさせたいけどどうしたらいいのかなーと調べたが、どうやら実物が必須らしかった。Amazonで購入して初期設定したあとはしまってある。

⚡️HomeBridge

それぞれ別のアプリから制御する必要があって不便だった。 iPhoneのHomeアプリから一括制御するためにnfarina/homebridgeを導入した。

部屋はひとつしかないがベッドルームということにしている。

f:id:mzp:20181010231934p:plain

HomeBridgeはRaspberryPiZeroWで動かしている。Raspberry Pi Zero W ケースキット - SWITCH-SCIENCEを買ったが、SDカードがついてないことを見落してて追加購入した。

セットアップ以下のサイトを参考にした。

モジュールは以下のものをいれた。

💕感想

一番の利点はいつも手元にあるiOSバイスから制御できるようになった点である。リモコンはどこに置いたか忘れがちだが、iPhoneiPadmacOSのどれかが手元にあることは多い。

Siri経由で操作できるけどそこまで便利ではない。

あとは以下のものをスマート家電化したいができてない。

  • スマートロック。Qrioがよさそうに見えるが。
  • コーヒーメーカー。自動応答するコーヒーメーカーは夢がある。RFC 2324 Hyper Text Coffee Pot Control Protocolをしゃべってほしい。
  • 洗濯機・炊飯器。残り時間をアプリから確認したい。

☀️ダイナミックデスクトップ壁紙

Mojaveのダイナミックデスクトップでは時刻によって壁紙が変化する。 これを用いてミクさんが部屋の中を歩きまわる壁紙を作成した。

f:id:mzp:20180926194346p:plain

(livetune feat. 初音ミク「Redial」Music Video - YouTubeより)

🖥ダイナミックデスクトップ

macOS Mojaveの紹介ページに記載されているとおり、Mojaveでは時間の経過に応じて壁紙が変化する。

f:id:mzp:20180926195049p:plain

WWDCのキーノートでも、生活にあわせて変化する壁紙のデモがあった。

f:id:mzp:20180926195305p:plain

f:id:mzp:20180926195314p:plain

f:id:mzp:20180926195323p:plain

🛠仕組み

ダイナミックデスクトップ用の壁紙は他の壁紙と同様 /Library/Desktop Pictures に配置されている。 異様にファイルサイズがでかい。

f:id:mzp:20180926195543p:plain

プレビューで開くと16枚の画像が確認できる。HEIFでは一つのファイルに複数の画像をまとめられるので、その機能を利用している。

f:id:mzp:20180926195613p:plain

それに加えてメタデータに時刻と画像の対応、より正確には太陽の位置と画像の対応を格納している。 ここに関しては、以下の記事が詳しい。

💕 作る

WallpaperEngineを使った以下の壁紙の再現を目指す。動きまわってるのがかわいい。

livetune feat. 初音ミク「Redial」Music Video - YouTubeをダウンロードしてフレームごとに分割する。

youtube-dl 'https://www.youtube.com/watch?v=243vPl8HdVk'
ffmpeg -i 'livetune feat. 初音ミク「Redial」Music Video-243vPl8HdVk.mkv' -f image2 %d.png

ここからいい感じの16枚を選ぶ。

f:id:mzp:20180926200339p:plain

複数の画像を束ねた上でメタデータを書き込むのは、mczachurski/wallpapper: Console application for creating dynamic wallpapers for macOS Mojaveで行なう。

brew tap mczachurski/wallpapper
brew install wallpapper

macOS Mojave dynamic wallpaper – ITNEXTを参考に定義ファイルを雑に作る。

info.json

[
  {
    "altitude": -0.3427528387535028,
    "azimuth": 270.9334057827345,
    "fileName": "1.png",
    "isPrimary": true,
    "isForLight": true,
    "isForDark": false
  },
  {
    "altitude": -10.239758644725045,
    "azimuth": 81.77588714480999,
    "fileName": "2.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": -4.247734408075456,
    "azimuth": 86.33545030477751,
    "fileName": "3.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": 1.3890866331008431,
    "azimuth": 90.81267037496195,
    "fileName": "4.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": 7.167168970526129,
    "azimuth": 95.30740958876589,
    "fileName": "5.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": 13.08619419164163,
    "azimuth": 99.92062963268938,
    "fileName": "6.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": 40.41563946490428,
    "azimuth": 129.18652208191958,
    "fileName": "7.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": 53.43347266172774,
    "azimuth": 182.2330942549791,
    "fileName": "8.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": 38.793128200638634,
    "azimuth": 233.5515919580959,
    "fileName": "9.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": 11.089423171265878,
    "azimuth": 261.87159046576664,
    "fileName": "10.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": 5.1845753236736245,
    "azimuth": 266.4432737071051,
    "fileName": "11.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": -6.248309374122789,
    "azimuth": 275.44204536695247,
    "fileName": "12.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": -12.20770735214888,
    "azimuth": 280.07031589401174,
    "fileName": "13.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": -39.48933951993012,
    "azimuth": 309.41857318745144,
    "fileName": "14.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": -52.75318137879935,
    "azimuth": 2.1750965538675473,
    "fileName": "15.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": false
  },
  {
    "altitude": -38.04743388682423,
    "azimuth": 53.50908581251309,
    "fileName": "16.png",
    "isPrimary": false,
    "isForLight": false,
    "isForDark": true
  }
]

このファイルを用いてダイナミックデスクトップ用の壁紙を生成する。

wallpapper -o ~/Pictures/redial.heic -i info.json *.png

システム環境設定から設定する。なぜかサムネ画像がおかしい。

f:id:mzp:20180926200606p:plain

時刻を変更して動作を確認する。

f:id:mzp:20180926194346p:plain

f:id:mzp:20180926194359p:plain

✨感想

動画を壁紙にする場合と比較して変化がおだやかなので、そこまで気がちらない。よい。

こちらを覗き込む画像は夜に表示されるようにしている。 なので、出社前は空の部屋で、家かえってきてPCをつけるとミクさんと目があうので、体験としてよい。

もうちょっと応用したいが、変化してたのしい壁紙のアイデアが足りない。WallpaperEngineのときもそういう話をしていた。

♨️銭湯

東京に引っ越したら銭湯によく行くようになった。東京にはいっぱい銭湯があってよい。

💼持ち物

タオルやシャンプーなどは置いてないことが多いので、自分のものを持っていく。 ダイソーで買ったビニールバッグにコンビニで買ったシャンプーセットを入れている。

水を持ち込んでる人もたまに見かける。 炭酸水を持ち込んで、飲み水と頭を洗うのに使ってて、なるほど感があった。

👍よい

  • 大きい風呂に入れる。
  • 10分くらい歩いたとこにあるので便利。ご飯を炊いてる間に風呂、とかをやっている。お風呂の四階に住む。 - 物件ファンほど近くはない。
  • 銭湯によっては温泉にはいれるとこもあってお得。
  • あまり混んでないので、雑に行ける。

🤔微妙

  • 銭湯の料金 > 水道代なので、やりすぎると破産するかもしれない。
  • 街中を歩いてた直後、店にはいって数十秒で全裸になるという行為に違和感を覚えてしまう。慣れない。
  • 結構話しかけられる。街中で話しかけられることはほぼないのに、全裸だと話しかけられるのおもしろい。