googleのロケーション履歴からヒートマップを作成してくれるgeo-heatmapが面白い

これ。 github.com

androidでかつ旅行とか出張が多い人はもっと面白い結果になるかも。 私は高専のとき千葉に住んでいて、大学院から東京に住んでいたのでそこらへんが赤くなっている。 旅行で台湾行ったりしたのも出ていて、懐かしいのも出てくる。 f:id:miettal:20191125083235p:plain f:id:miettal:20191125083222p:plain

ロケーション履歴のダウンロード

readmeの通りにやるだけ

f:id:miettal:20191125083538p:plain f:id:miettal:20191125083548p:plain f:id:miettal:20191125083557p:plain

geo-heatmap準備&実行

readmeの通りにやるだけ

$ git clone https://github.com/luka1199/geo-heatmap
Cloning into 'geo-heatmap'...
remote: Enumerating objects: 156, done.
remote: Counting objects: 100% (156/156), done.
remote: Compressing objects: 100% (124/124), done.
remote: Total 156 (delta 81), reused 75 (delta 27), pack-reused 0
Receiving objects: 100% (156/156), 4.10 MiB | 1.29 MiB/s, done.
Resolving deltas: 100% (81/81), done.

$ geo-heatmap/

$ pip install -r requirements.txt
Collecting folium
  Downloading https://files.pythonhosted.org/packages/72/ff/004bfe344150a064e558cb2aedeaa02ecbf75e60e148a55a9198f0c41765/folium-0.10.0-py2.py3-none-any.whl (91kB)
     |█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ | 92kB 1.2MB/s
Requirement already satisfied: progressbar2 in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from -r requirements.txt (line 2)) (3.47.0)
Requirement already satisfied: requests in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from folium->-r requirements.txt (line 1)) (2.22.0)
Collecting branca>=0.3.0
  Downloading https://files.pythonhosted.org/packages/63/36/1c93318e9653f4e414a2e0c3b98fc898b4970e939afeedeee6075dd3b703/branca-0.3.1-py3-none-any.whl
Requirement already satisfied: numpy in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from folium->-r requirements.txt (line 1)) (1.17.4)
Requirement already satisfied: jinja2>=2.9 in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from folium->-r requirements.txt (line 1)) (2.10.3)
Requirement already satisfied: six in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from progressbar2->-r requirements.txt (line 2)) (1.11.0)
Requirement already satisfied: python-utils>=2.3.0 in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from progressbar2->-r requirements.txt (line 2)) (2.3.0)
Requirement already satisfied: chardet<3.1.0,>=3.0.2 in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from requests->folium->-r requirements.txt (line 1)) (3.0.4)
Requirement already satisfied: urllib3!=1.25.0,!=1.25.1,<1.26,>=1.21.1 in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from requests->folium->-r requirements.txt (line 1)) (1.23)
Requirement already satisfied: idna<2.9,>=2.5 in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from requests->folium->-r requirements.txt (line 1)) (2.7)
Requirement already satisfied: certifi>=2017.4.17 in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from requests->folium->-r requirements.txt (line 1)) (2018.8.24)
Requirement already satisfied: MarkupSafe>=0.23 in /Users/taisyo/.pyenv/versions/3.6-dev/lib/python3.6/site-packages (from jinja2>=2.9->folium->-r requirements.txt (line 1)) (1.0)
Installing collected packages: branca, folium
Successfully installed branca-0.3.1 folium-0.10.0

$ unzip ~/Downloads/takeout-20191124T232243Z-001.zip

$ python geo_heatmap.py ~/Downloads/Takeout/ロケーション履歴/ロケーション履歴.json
Loading data from /Users/taisyo/Downloads/Takeout/ロケーション履歴/ロケーション履歴.json...
|########################################################################################################################################################################################################################################|100% Time:  0:00:00
Generating heatmap...
Saving map to heatmap.html...
Opening heatmap.html in browser...

2018年に行ったお店リスト

毎月高専の友達とうまい飯を食ってる。 その記録。

f:id:miettal:20190120183026j:plain

2月

テーマ: 肉 お店: コンコンブル 場所: 渋谷

とりあえず修論終わってうまいものを食べたかったので、食べログで肉の断面がうまそうな店を探していった。お店の雰囲気良い。うまかった。 tabelog.com

3月

テーマ: 魚 お店: 山葵(わさび) 場所: 人形町

中野のマグロマート(後述)に行きたかったのだが、予約満員で予約できなかったので、同じ魚系で探して行った。ざ、和食魚料理な感じ。うまかった。 tabelog.com

5月

テーマ: 魚 お店: マグロマート 場所: 中野

マグロのワンダーランド、すべてのメニューがマグロ。店が魚臭いが、絶品。うまかった。他のメニューも試したいので、もう一度行きたい。 tabelog.com

7月

テーマ: ベトナム お店: サイゴン・レストラン 場所: 池袋

この会から毎月いろんな国の料理を食べようということに。うまかった。 tabelog.com

8月

テーマ: タイ お店: はすの里 新御徒町本 場所: 御徒町

ベトナムとの違いがよくわからず。うまかった。 tabelog.com

9月

テーマ: トルコ お店: イズミル 場所: 阿佐ヶ谷

お店の雰囲気良い。うまかった。もう一度行きたい。 tabelog.com

10月

テーマ: モンゴル お店: シリンゴル 場所: 巣鴨

羊づくしでした。うまかった。 tabelog.com

11月

テーマ: イギリス お店: ザロイヤルスコッツマン 場所: 飯田橋

外人多い気がする。フィッシュポテト!うまかった。 tabelog.com

12月

テーマ: ロシア お店: ロゴスキー 場所: 銀座

お店の雰囲気良い。ウィスキーたくさん。うまかった。 tabelog.com

総評

うまかった。

3000円の体重計で、体重計に乗ったらツイートされるようにする

だいたいこんな感じで動く

体重計→体重系スマホアプリ→fitbit→ifttt→twitter

利点: サーバレス

欠点: 体重計に乗るときはスマホ必須

体重計を買う

MASARUの体重体組成計を買う。 これ、bluetoothで通信できて、専用アプリに記録したり、iOS Healthやfitbitと連携できる。 そんでもって3000円で激安。

www.amazon.co.jp

体重計のスマホアプリをインストールする

MASARUをインストールする。 体組成計なので、結構いろいろ測れて面白い。

f:id:miettal:20190118003238j:plain

fitbitに登録する

fitbitってwearable deviceの印象が強かったけど、健康管理プラットフォームなクラウドもやってるんだー。

www.fitbit.com

体重計のスマホアプリをfitbitに連携する

体重を測るとスマホ経由でfitbitにアップロードされるようになる。

f:id:miettal:20190118003254j:plain

iftttにルールを書く

fitbitに体重がアップロードされたら、それをツイートするルールを書く。 iOS Healthはトリガー対応してないから、fitbitでやらざるを得ないんだよねー。

f:id:miettal:20190118003841p:plain

体重計に乗る

ツイートされる。 おー。

Nature Remoに存在しないボタンを登録する

我が家の照明は、リモコンで操作できる。 なので、Nature Remoからももちろん操作できるのだが、リモコンにあるボタンはON/OFFを切り替えるボタンしかない。 そのため、明示的にONにしたり、OFFにしたりすることはできない。 しかし、同じメーカーの他の照明リモコンを見てみるとONのボタンとOFFのボタンが存在している。 これは推測だが、おそらくプロトコル上、実装上存在しているが、廉価なモデルのリモコンのため省略されていると考えられる。 そこで、Nature RemoのAPIを駆使し、物理的に存在しないが、プロトコル上、実装上存在していると考えられるONボタンとOFFボタンを調査し、Nature Remoから照明のONとOFFを操作できるようにする。

f:id:miettal:20190112150510j:plain
MARUZEN ELECTRIC CO., LTD. MD3

Nature RemoのIPアドレスを調べる

IPアドレスがわからないと、Local APIを叩けないので調べる。

まず、Nature Remoと同じネットワーク(LAN)につなぐ。

つぎに、mDNSでサービスタイプから、インスタンス名を引く。

$ dns-sd -B _remo._tcp
Browsing for _remo._tcp
DATE: ---Sat 12 Jan 2019---
13:33:50.261 ...STARTING...
Timestamp A/R Flags if Domain Service Type Instance Name
13:33:50.413 Add 3 5 local. _remo._tcp. Remo-4A8BDD
13:33:50.413 Add 2 5 local. _remo._tcp. Remo-9086AB

リビングルームダイニングルームのNature Remoが見つけられる。

最後に、mDNSでインスタンス名からIPv4アドレスを引く。

$ dns-sd -G v4 Remo-4A8BDD.local 
DATE: ---Sat 12 Jan 2019---
13:40:19.941  ...STARTING...
Timestamp     A/R    Flags if Hostname                               Address                                      
TTL
13:40:19.942  Add        2  5 Remo-4A8BDD.local.                     192.168.0.18                                 120

これでリビングルームのNature RemoのIPアドレスがわかった。

受信した信号を見る

Nature Remoに対して、照明リモコンのON/OFFボタンを押して、赤外線信号を送信する。

Nature Remo Local APIのmessage APIで、Nature Remoが受信した信号を見る。

$ curl -s -X GET 'http://192.168.0.4/messages' -H 'X-Requested-With: curl'
{"format":"us","freq":37,"data":[4675,2627,564,959,572,1850,563,1863,562,958,564,1860,571,1849,566,956,575,949,564,959,571,1849,574,1850,575,952,571,1850,570,1854,572,1849,564,961,571,1847,568,1859,568,951,566,955,573,951,565,955,564,965,562,1854,576,948,567,956,565,957,572,950,571,951,571,953,570,950,572,945,572,16480,4670,2633,565,959,562,1860,572,1848,574,952,571,1849,573,1851,573,949,573,950,564,956,575,1849,576,1850,562,959,575,1848,572,1853,562,1857,566,959,572,1848,567,1859,570,951,571,953,563,956,565,959,570,951,567,1858,571,949,573,951,564,957,572,949,575,949,564,955,574,948,574,944,566,16484,4682,2624,571,951,564,1859,564,1858,572,952,571,1849,569,1855,573,951,571,949,566,960,562,1857,577,1849,570,951,565,1861,569,1850,574,1849,576,946,575,1849,573,1850,571,953,565,955,573,950,574,947,575,946,574,1852,570,953,570,949,566,959,563,956,573,952,563,959,564,960,569,943,574]}

data配列の各要素は、赤外線ONの期間、OFFの期間、ONの期間、OFFの期間、、、、を表している。 厳密には、これは38kHzの変調をデコードしたあとの結果である。実際にはONの期間は38kHzの変調信号になっている。

プロトコルの詳細は書きを参照されたい。

elm-chan.org

そして、この結果を、ぱっと見た感じ、

NECフォーマット
T=560us
リーダーコード: ON:4T, OFF:8T,
ビット0: ON: 1T, 1.7T
ビット1: ON: 1T, 3.4T

であることがわかる。

プログラムに噛ませて赤外線信号を16進4バイトのデータにデコードする。

$ cat decode.py
import sys
import json

obj_txt = sys.stdin.read()
obj = json.loads(obj_txt)

bit_array = [int(not x < 1100) for x in obj['data'] if 900 < x < 2000]

byte_array = [sum([x<<i for (i, x) in enumerate(x)]) for x in zip(*[iter(bit_array)]*8)]
print(' '.join(["{:02x}".format(x) for x in byte_array]))

$ curl -s -X GET 'http://192.168.0.4/messages' -H 'X-Requested-With: curl' | python decode.py
36 76 83 00 36 76 83 00 36 76 83 00

これで、データがわかった。これをすべてのボタンに対して行い、ボタンに対応するデータの一覧を得る。

(ch1)

Button Data
豆球 36 76 48 00
オフタイマー60 36 76 80 00
オフタイマー30 36 76 82 00
ON/OFF 36 76 84 00
明るく 36 76 86 00
暗く 36 76 88 00

(ch2)

Button Data
豆球 36 76 47 00
オフタイマー60 36 76 8f 00
オフタイマー30 36 76 81 00
ON/OFF 36 76 83 00
明るく 36 76 85 00
暗く 36 76 87 00

これらの結果から、おそらく3バイト目が照明の各ボタンの操作に対応していると推察できる。 これから、この3バイト目を書き換えて、赤外線を送信し、ONとOFFに対応するデータを探す。

受信した信号を見る

プログラムに噛ませて16進4バイトのデータを赤外線信号にデコードする。 そして、Nature Remo Local APIのmessage APIで、照明に対して赤外線を送信する。

$ cat encode.py
import sys
import json

byte_array = [int(x, 16) for x in sys.stdin.read().split(' ')]

data = [4668,2636]
for x in byte_array:
    for i in range(8):
        b = (x>>i)&1
        if b == 1:
            data += [562, 1877]
        else:
            data += [562, 951]
data += [548, 16504]

obj = {
    'format': 'us',
    'freq': 37,
    'data': data,
}

obj_txt = json.dumps(obj)
sys.stdout.write(obj_txt)

$ echo "36 76 83 00" | python encode.py | curl -X POST 'http://192.168.0.4/messages' -H 'X-Requested-With: curl' -d @-

無事照明が点いた(消えていたのでON/OFFボタンで点灯した)。

3バイト目を書き換えてみて、動作するボタンと、動作内容の一覧を得る。

(ch1)

Button Data
ON 36 76 42 00
OFF 36 76 4a 00

(ch2)

Button Data
ON 36 76 41 00
OFF 36 76 49 00

41がON、49がOFFに対応していることがわかった。 これから、Nature Remo Cloud APIを使って、Nature Remoにボタンを登録する。

信号を登録する

まず、Nature Remo Global APIのアクセス用のトークンを生成し、取得する。以降トークンはXXXX(伏せ字)と表す。

Home

次に、Nature Remo Global APIののappliances APIで、機器の一覧を見る。

$ curl -s -X GET "https://api.nature.global/1/appliances" -H "Authorization: Bearer XXXX"  | python -mjson.tool

(snip)
    {
        "id": "YYYY",
        "device": {
            "name": "Living    Room",
            "id": "XXXX",
            (snip)
        },
        "model": null,
        "type": "IR",
        "nickname": "Living Room Light",
        "image": "ico_light",
        "settings": null,
        "aircon": null,
        "signals": [
            (snip)
        ]
    },
(snip)

機器のIDがわかる。以降機器のIDはYYYY(伏せ字)とする。

最後に、Nature Remo Global APIののsignals APIで、赤外線信号を登録する。

$ echo "36 76 41 00" | python encode.py | urlencode
 %7B%22format%22%3A%20%22us%22%2C%20%22freq%22%3A%2037%2C%20%22data%22...5D%7D
$ curl -s -X POST "https://api.nature.global/1/appliances/YYYY/signals" -H "Authorization: Bearer XXXX" -d "message=ZZZZ&image=ico_on&name=ON"

{"id":"XXXX","name":"ON","image":"ico_on"}

$ echo "36 76 49 00" | python encode.py | urlencode
%7B%22format%22%3A%20%22us%22%2C%20%22freq%22%3A%2037%2C%20%22data%22...5D%7D
$ curl -s -X POST "https://api.nature.global/1/appliances/YYYY/signals" -H "Authorization: Bearer XXXX" -d "message=ZZZZ&image=ico_off&name=OFF"
{"id":"XXXX","name":"OFF","image":"ico_off"}

無事、ONのボタンと、OFFのボタンが登録された。

f:id:miettal:20190112150706p:plain
Nature Remo App

ぱちぱちぱち

gem install で certificate verify failed と出てgemがインストールできないのを直す

応急処置的だが。

こんなの出る

$ gem install rubocop
ERROR:  Could not find a valid gem 'rubocop' (>= 0), here is why:
          Unable to download data from https://rubygems.org/ - SSL_connect returned=1 errno=0 state=error: certificate verify failed (https://api.rubygems.or│

g/latest_specs.4.8.gz)

updateもできない

$ gem update --system
ERROR:  While executing gem ... (Gem::RemoteFetcher::FetchError) 
    SSL_connect returned=1 errno=0 state=error: certificate verify failed (https://api.rubygems.org/specs.4.8.gz)

sourceからhttpsを消して、httpを入れる

$ gem sources --remove https://rubygems.org/
https://rubygems.org/ removed from sources

$ gem sources -a http://rubygems.org/ 
https://rubygems.org is recommended for security over http://rubygems.org/

Do you want to add this insecure source? [yn]  y
http://rubygems.org/ added to sources

updateする

$ gem update --system
Updating rubygems-update
(snip)

RubyGems system software updated

sourceからhttpを消して、httpsを入れる

$ gem sources --remove http://rubygems.org/
http://rubygems.org/ removed from sources

$ gem sources -a https://rubygems.org/
https://rubygems.org/ added to sources

直った

$ gem install rubocop
Fetching: jaro_winkler-1.5.1.gem (100%)
(snip)