丁寧に手を抜く

頑張らない努力

React Nativeで手軽に別スレッドでコードを実行する方法

English version available here:

dev.to

React NativeアプリはJavaScriptで組まれるから、1つのスレッドしかない。 だからCPUヘビーなタスクを実行するとUIをブロックしてしまう。例えばインデクシングとか。 バックグラウンドでJSを実行したければ、react-native-workersみたいなネイティブモジュールを使う必要がある。これはWeb WorkerみたいなAPIを提供してくれるモジュール。でも、単純なコードを実行したいだけならオーバースペック感がある。あと、むやみやたらにネイティブモジュールを増やしたくない。アプリが複雑になるから。expoアプリを作っている人はそもそもネイティブモジュールが使えない。

で、気付いたんだけど、WebViewを使えばいいじゃないか、と。 WebViewには injectJavaScript というメソッドがあって、これを使えば任意のコードをwebview内で実行できる。 しかもアプリのUIスレッドをブロックしない。 なぜならwebviewの中身はSafari(iOS)/Chrome(Android)の別インスタンスだから。 それを確認するために以下のコードを両方のプラットフォームで実行してみた。仮説は当たった:

for (;;) { Math.random() * 9999 / 7 }

そうと気づけばWebViewはとても便利。ネイティブモジュールいらずでJSをバックグラウンド実行できる。 例を以下に示す:

import React, { Component } from 'react'
import { WebView } from 'react-native'

export default class BackgroundTaskRunner extends Component {
  render() {
    return (
      <WebView
        ref={el => this.webView = el}
        source={{html: '<html><body></body></html>'}}
        onMessage={this.handleMessage}
      />
    )
  }
  runJSInBackground (code) {
    this.webView.injectJavaScript(code)
  }
  handleMessage = (e) => {
    const message = e.nativeEvent.data
    console.log('message from webview:', message)
  }
}

コードの実行結果を受け取りたい時は onMessage propを上記のようにwebviewに追加すればいい。 webview側では window.postMessage 関数があって、これを呼び出すと渡したデータがアプリ側に送信される。

以下のようにwebview側で呼び出す:

const message = { ok: 1 }
window.postMessage(message)

するとアプリ側で以下のように結果が得られる:

message from webview:, { ok:1 }

ただし、データの受け渡しにオーバーヘッドが見込まれるので注意する必要がある。巨大なデータを何も考えずに JSON.stringifyJSON.parse すると本末転倒なので気をつける必要がある。 まあ、これはネイティブモジュールを使った場合でも似たような問題かもしれないけど。

That's it! 😄

健康については英語で検索したほうが質の良い情報が見つかる事に気づいた

2016年末に炎上したDeNAの医療系サイトWELQに代表される日本の健康系情報サイトはアフィ目的で作られたものばかりで、素人臭のする信頼性の欠けたものばかりが未だに多くのさばっている。

似たような文書フォーマットで前置きが長くて、同じことを言い方を変えて何回も繰り返し書く傾向が強い。学術的な引用など皆無。

ふと思いつきで英語で検索したらとても良質な情報にすぐにありつけて感動した。

例えば「work out stress」で検索するとこんなページが出て来る:

www.mayoclinic.org

友人曰く、MAYO CLINICは潤沢な資金をもって先進的な医療研究を行っている病院らしい。

内容も読みやすくてわかりやすい。

他には「workout before bed」で検索すると以下のようなサイトが上位にヒットする:

Should You Work Out Before Bed? | Get-Fit Guy

文章を読むとちゃんと研究や確立された理論に基づく書き方をしていて納得感が持てる。

がんばって探せば日本語でもこういうちゃんとしたサイトがあるけど、いちいちちょっとした身近なことをそうまでして調べるモチベーションは正直ない。

だから日本依存のトピック以外はこれから出来るだけ英語で調べようと思う。そのほうが英語の勉強にもなるし。

フィリピンの人から突然スタートアップに誘われたけどお断りした

最近あった出来事。

ある日Facebookで知らない外国人からメッセージが飛んできた。

読んでみると、俺をmeetup.comで見つけたと。んで面白そうだから連絡してみた、という。

どうやって見つけたのか詳しく聞くと、一時期よく参加していた以下のイベントで見つけたらしい。

www.meetup.com

これはプログラミングを教える代わりに英会話の練習相手になってもらうという趣旨のイベント(勉強会)。

そして俺をFacebookで検索して見つけて、ホームページに飛んで、俺の仕事やパッションに感心したのだとか。

で、俺はその時、てっきりこの人はプログラミングが学びたくて連絡してきたんだなと思っていた。

とりあえず挨拶代わりにビデオコールしようよ、としきりに誘うので、まぁ、久々の英語の練習になるし軽くならいいか〜と夜22時に通話を開始した。

フィリピンの一般家庭のネット速度は日本で言うとまだまだ10年前ぐらいらしく、Facebook Messengerでは速度が足りずまともに会話できなかった。

WeChatなら軽いと言うのでインストールして登録して再度通話開始した。おお、すごい、こっちなら話せる。WeChatすごい。

んで挨拶を交わして軽く雑談したあと、予期せぬ事が起こった。

なんかやりたいことがある、と。

あー、ウェブサイトかなんかかな?プログラミング初心者だし、と思っていたけど全然違った。

そこから延々二時間以上、スタートアップのアイデアを聞かされた。

なんかセキュリティ系と広告モデルを組み合わせたサービスのアイデアで、Airbnbとかではびこる詐欺を未然に防ぎつつ、ユーザに広告を見せるインセンティブを与えるというものだった。

この説明では意味が分からないと思うけど、詳しく書いても仕方がないので割愛する。

すごい勢いで話すので聞き取れなくて、何度も同じことを噛み砕いて説明してもらった(親切)。

でも内心「はよ寝たい」と思っていた。わいの思ってたんと違う。

提案は、俺にそのプロジェクトのco-founderになってもらって、プロトタイプを作って欲しいという事だった。

そのプロトタイプをもとに、出資を集めたいらしい。

一通り話を聞いたあと、とりあえず「そのアイデア面白いと思う。でも回答は時間ちょうだい」と言って通話を切った。

最終的には以下のように言って断った。


Hi, Thanks for inviting me to your project. I appreciate that you tried to explain your idea to me regardless of my poor English listening skill. I've considered about joining your project, but I'm afraid that I can't be your co-founder. The biggest reason is that I basically like to work on what I want. I've never encountered any scams online in Japan. It totally depends on my personal experience and not depends on how interest your project is. It'd be better to find someone who has a similar problem with you to solve. Because I think great solutions would always come with willingness to solve the problem. Sorry, but I hope your project will soon get angels.


話を聞くのも、断るのも結構神経とエネルギーを使うので疲れた。

てっきりプログラミングを学びたい人だと思っていたので、なんだか肩透かしを食らった気分。

今回は英語のいい練習になったからよかった。でももしそうでなかったら一方的に興味のない話をされただけで、時間の無駄になるところだった。

というか、知らない外国の人がいきなりコールしてきて、スタートアップの話をきいて、いくらそれが面白くても「じゃあ、やろうかな」とはならんよ。音信不通になったら終わりだし。

気に入ってくれたのは嬉しいんだけどなぁ。相手も悪い人ではないんだろうけど。パートナーの探し方、もうちょっと考えたほうがいいよね。

この手の連絡には今後気をつけたい。

以上、WeChatは素晴らしいという日記でした。

英語のリスニングがなぜ苦手なのかに気づいたらだいぶ聞き取れるようになった

f:id:craftzdog:20180226155743j:plain

自分は英語の読み書きはそこそこ出来るものの、普段英会話をしないので聞き取りが大変苦手。

でも最近は昔と比べてだいぶ聞き取れるようになったと思う。あることに気をつけるようにしたから。

それは単純なことで、「文章の初めを注意して聞く」ということ。

英語が得意な人なら当たり前かもしれない。

日本語は構文上、大事なことを最後に言う傾向がある。

例えば、日本語は文が否定形かどうか最後まで聞かないと分からない。疑問形も語尾で決まったりする。

それに対して、英語は初めに肯定形か否定形、あるいは疑問形かがわかる。

英語は、後付けで状況とか雰囲気とかを付け加えていく語順で、後ろに行くにつれてどんどん重要度が下がる。結論を先に言って、理由をあとで述べる傾向が強い。

だから、最初さえ聞き逃さなければあとは推測でなんとなく補完できる。

自分が英語のリスニングが苦手だった理由は、日本語と同じ感覚で英語を聞いていたからだった。

最初を聞き流して、後から大事な単語が出て来ると思って集中して聞いていた。

そうじゃなくて、最初を集中して聞いて、後は付加的な情報なんだと思って聞くと自然と入ってくるようになった。

余談だけど、この人の英語は聞き取りやすくてすごくいい練習材料になる:

www.youtube.com

Mayukoさんはソフトウェアエンジニアとして海外で働いている人。

いきいきと喋る様子にとても好感が持てる。

こんな風に喋れるようになりたいね。

無骨なのに落ち着くカフェ From afar 倉庫01 @ 浅草

f:id:craftzdog:20180219211722j:plain

大江戸線蔵前駅から徒歩10分ほどに位置するカフェ「From afar 倉庫01」

名前の通り倉庫を改装した作りになっていて、トタンや鉄筋がむき出しになっている。

f:id:craftzdog:20180219212156j:plain

それなのに、店内は落ち着いた雰囲気でオシャレ。

f:id:craftzdog:20180219211751j:plain

周りには雑誌や絵本などが飾られていて自由に読めるようになっている。 読書をゆっくりしたり、お喋りを楽しむのに最適な空間。

f:id:craftzdog:20180219211825j:plain

店員さんも丁寧な応対で終始好印象のカフェでした。


Instagramカフェ巡り写真を投稿しています。よかったらフォローしてね。


ゆっくり走ったらジョギングが続くようになった

f:id:craftzdog:20180215232609j:plain

以前、ゆっくり動作を心がけると宣言してはや数週間。 これまでよかったなと感じたことは下記に書いた。

craftzdog.hateblo.jp

健康のため、ストレス発散のためにジョギングをしている。 以前から何度か習慣づけようとがんばったけど、いつも1週間程度で続かないという問題に悩んでいた。 でも今回は続いている。 しかも楽しい。 今までは面倒としか思わなかったのに。

毎朝、起きて、コップ一杯の水を飲んで、走る。

なぜ続いているのか。 それは単純に走るスピードをゆっくりにしたから。 今まではどうやらハイペースすぎたっぽい。 キツすぎた。 だから楽しくなかった。

自分は学生の頃水泳部だったためか、なんか運動にもスピードを求める癖があったのが理由かもしれない。 追い抜かされると悔しくなってペースを上げようとしたり。 タイムにこだわったり。 もう選手じゃないんだからそんなの要らないのにね。

この教訓から得た、ジョギングを続けるための大事な点はこれだと思う:

  • タイムや距離にこだわらない
    • 選手じゃないから
    • ゆっくり走る
  • 走る時間の長さを基準にコースを決める
    • 自分の場合は30分間ぐらいがちょうどいい
  • 汗をかくことを目的とする
    • あくまで健康やストレス発散のため

朝に走ると快眠効果もあることが研究で実証されている:

In fact, people who work out on a treadmill at 7:00am sleep longer, experience deeper sleep cycles, and spend 75 percent more time in the most reparative stages of slumber than those who exercise at later times that day.

これは自分も実感できる。 心地よい疲労感に包まれて眠れる。 この事は、この記事をきっかけに知った:

あと、就寝3時間前を過ぎたら仕事のことは考えないようにするとなお良い。

体は資本なのでこの習慣は大事にしたい。

Raspberry Pi 3 + Google Homeでエアコンを音声操作できた

先月届いた赤外線センサーキット。

craftzdog.hateblo.jp

夜の空いた時間を使ってやっと開封してセットアップしたのでその過程をシェアしたい。

f:id:craftzdog:20180213103545j:plain

なんか・・生き物みたいやな・・笑

上にぴょんぴょん出ているのは温度や気圧、湿度、明度のセンサー。今回は使わないけど、部屋のコンディション記録をのちのち取ろうと計画してる。

今回は前面に付いている赤外線送受信器を使う。

成果

たのしー!!リモコンが要らなくなって部屋がちょっとすっきりした。リモコンって地味に邪魔だよね。

アーキテクチャ

f:id:craftzdog:20180213111707p:plain

  1. Google Homeで音声入力を受け付ける
  2. IFTTTでwebhookを実行 (HTTPで指定したURLを叩く)
  3. Raspberry PiでIR送信コマンドを実行

Raspberry Piと赤外線センサーのセットアップ

このあたりを参考にしてセットアップした。 ANAVI Infrared pHATのセットアップ手順はマニュアルの通りなので本稿では割愛する。

LIRCを使ってリモコンを学習

LIRCは "Linux Infrared Remote Control" の略で、その名の通り赤外線リモコンのライブラリ。 pHATのマニュアルに従ってインストールした。使用するモジュールによってステップは異なるので割愛する。 今回は、MITSUBISHI 霧ヶ峰のリモコンを学習させたい。

f:id:craftzdog:20180213114946j:plain

IR受信器を手動で動かして、信号を読み取る。 まずはLIRCのsystemdサービスを止める:

sudo systemctl stop lircd

IR生データをstdoutに出力:

mode2 -m -d /dev/lirc0

ほんで、受信器に向けてリモコンのボタンを押下すると、以下のような出力が得られる:

4989552

     3046     3019     3052     4278      615     1683
      552      554      581     1675      573      718
      552      555      579     1676      573      561
      573     1814      580     1676      603     1666
      572      561      584      691      569      554
      580      553      571      562      572      693
      577      556      579      554      600     1669
      581     1806      577      557      578      555
      579      554      580     1833      551      556
      579      554      580      554      601      677
      572      561      573      560      575     1680
      579     1808      575      559      576      556
      578      557      578      686      605     1664
      573     1682      577      557      586      678
      573     1683      576     1679      579      555
      580     1806      577     1688      591     1670
      579     1677      571      693      577      557
      577      556      579     1685      563      694
      576      556      578      556      600      547
      577      687      573      561      573      559
      583      551      576      713      546      562
      573      587      547      559      607      695
      575     1680      579      564      570     1676
      572      717      574      559      575      585
      550     1678      581     1683      596

なんじゃこりゃああ。よくわからんけど、これはhighとlowレベルのIR信号の長さを示しているらしい。 しかしながら、同じボタンを押下しても毎回微妙に違うデータが出力されるので、精度は100%という訳ではなくノイズなどが影響するっぽい。 とりあえず電源のON/OFFの二種類の信号データを取得してメモした。 注意点は、1行目のどデカい数値は不要なので無視すること。この例では4989552を削除する。

次に aircon.lircd.conf というファイルを /etc/lirc/lircd.conf.d/ に作成する。内容は以下の通り:

begin remote

  name   aircon
  flags  RAW_CODES
  eps     30
  aeps   100

  ptrail   0
  repeat 0 0
  gap 28205
  frequency    38000

  begin raw_codes

    name on
     3427     1668      450     1248      446     1255
      450      388      449      389      448      414
      423     1250      444      394      443      420
      417     1257      448     1250      445      419
      417     1255      449      389      449      389
      450     1248      445     1256      450      387
      450     1056      702     1191      451      386
      450      414      426     1246      444      408
      438      383      445     1257      450      413
      423      413      423      414      424      388
      446      389      450      415      420      395
      442      428      411      414      425      385
      448      390      448      387      448      417
      420      391      446      417      423      387
      448      389      458     1240      457      383
      441      393      443     1256      448      421
      426      379      449     1251      442      399
      440      424      411      396      441      422
      414      422      415      408      430      426
      411      394      443     1257      448     1252
      506      331      441      421      421      401
      431      396      443      395      440      425
      411      423      415      423      414      441
      395      423      424      414      421      389
      451      389      452      393      439      386
      451      391      446      386      449      387
      449      388      450      405      441      412
      425      389      439      386      450      414
      433      390      436      415      431      385
      444      386      449      390      459      376
      450      414      424      392      442      388
      456      383      454      407      430      408
      429      409      421      387      449      390
      445      389      448      415      422      387
      450      388      448      422      415      415
      421      390      447      389      448      389
      448      415      422      389      448      389
      447     1255      444      389      443


    name off
     3431     1669      449     1248      447     1255
      449      416      422      389      453      410
      423     1248      508      331      442      394
      442     1152      553     1253      441      422
      426     1252      449      409      421      391
      445     1253      452     1248      447      391
      451     1222      477     1249      445      419
      418      393      443     1256      450      414
      422      394      443     1249      445      394
      443      394      442      394      449      420
      412      392      443      395      449      414
      416      395      456      381      441      396
      439      424      419      390      463      374
      459      417      415      386      453      382
      460      403      423      414      423      387
      453      415      421     1247      444      400
      437      393      454     1245      509      333
      443      417      426      411      425      385
      447      392      445      390      446      394
      443      417      419     1252      451     1251
      447      390      446      417      424      396
      436      391      446      391      445      417
      420      418      417      419      419      419
      417      396      442      425      411      393
      444      393      451      386      443      394
      442      420      415      397      442      394
      442      395      442      394      443      394
      441      401      435      397      440      423
      416      401      434      397      451      412
      428      382      451      386      451      387
      450      384      452      387      452      417
      420      409      425      393      456      375
      452      386      448      388      458      379
      447      397      438      394      445      421
      413      420      417      389      448      389
      448      391      445      417      420     1252
      447     1227      473     1253      505     1195
      446      417      420      391      450
  end raw_codes
end remote

Looks good. そしたらLIRCのsystemdプロセスを起動する。

sudo systemctl start lircd

ちゃんとリモコンが登録されているか確認する:

irsend LIST aircon ""

0000000000000001 on
0000000000000002 off

LIRCでIR信号を送信

では早速送信してみる:

irsend SEND_ONCE aircon on # 電源オン
irsend SEND_ONCE aircon off # 電源オフ

エアコンが反応したら成功。 Pretty neat.

自分の場合は一発ではうまく行かず、何度か信号を取り直す必要があった。諦めずにトライだ。

REST APIでネット経由でリモコンを操作できるようにする

Raspberry Piで簡単なnode.jsのサーバを立てて、HTTPリクエストを受け付けるようにした。 ソースコードGitHubに公開したので再利用ご自由に。

github.com

以下のようにkoaでルータを書いてリクエストを受け付けるようにした:

  router.put('/aircon/power', async ctx => {
    const cmd = spawnSync('/usr/bin/irsend', ['SEND_ONCE', 'aircon', 'on'])
    if (cmd.error) {
      logger('irsend error:', cmd.error)
      ctx.body = {
        ok: false,
        message: 'irsend command failed',
        result: cmd.error.toString()
      }
    } else {
      ctx.body = {
        ok: true,
        message: 'The aircon switched ON',
        result: cmd.stdout.toString()
      }
    }
  })

  router.delete('/aircon/power', async ctx => {
    const cmd = spawnSync('/usr/bin/irsend', ['SEND_ONCE', 'aircon', 'off'])
    if (cmd.error) {
      logger('irsend error:', cmd.error)
      ctx.body = {
        ok: false,
        message: 'irsend command failed',
        result: cmd.error.toString()
      }
    } else {
      ctx.body = {
        ok: true,
        message: 'The aircon switched OFF',
        result: cmd.stdout.toString()
      }
    }
  })

家のルータでポートマッピングを設定して準備完了。

IFTTTを使ってGoogle Homeから音声入力できるようにする

IFTTTでアプレットを作成する。

Google Assistantを設定

IFTTTとGoogle Homeを連携させる方法はこの辺などを参考にする:

IFTTTの Google Assistant サービスを追加。

以下のように音声入力を設定:

f:id:craftzdog:20180213114230p:plain

Webhookを設定

f:id:craftzdog:20180213114358p:plain

これで準備完了。

"Hey Google, make my room warm" と呪文を唱えれば、無事エアコンが起動。Super duper!!!

FUTURE WORK

  • エアコンの起動時間を記録する
  • 他のセンサーを使って部屋のコンディションを記録する
  • グラフ化して見れるようにする

Google の Cloud Firestoreが用途に適していそうな気がする。