お気持ちの表明

思考を雑に外出していきます

Raspberry Pi Zero Wのセットアップ

あらまし

最近、リモート勤務が多くなって、自宅にいる時間が長くなってきたので、家環境をいい感じにしたい気がしていました。 冬休みを長めに取ったし、冬休みのお勉強がてら、おうちハックをキメてこうと思って、Raspberry PiのZero Wと3を買いました。

今日はとりあえず、Zeroのほうのセットアップをしたので、忘備録として書いとこうと思います。

やったこと

  • 家に既にあったRaspberry Piのホスト名を変えておく
  • はんだ付けする
  • イメージをSDに書き込む
  • Host名を書き換える

家に既にあったRaspberry Piのホスト名を変えておく

私の家には、家に既にRaspberry Pi Model Bがある。
電源を入れてsshしようとすると、avahi-daemonで使っているホスト名が被って繋がらなさそうな気がした。

とりあえず、今稼働中のRaspberry Piのホスト名を変更する。 ホスト名を変更したければ、/etc/hosts/etc/hostnameに記載されているraspberrypiという名前の箇所を任意に書き換えればいいので、書き換える。 今そのRaspberry Piは、低温調理機以外の運用をしていないので、sous-vide-cooker-piと命名しといた。

以下、元あったRaspberry Piの書き換えたファイル

$ cat /etc/hostname
sous-vide-cooker-pi

$ cat /etc/hosts
127.0.0.1   localhost
::1     localhost ip6-localhost ip6-loopback
ff02::1     ip6-allnodes
ff02::2     ip6-allrouters

127.0.1.1   sous-vide-cooker-pi

はんだ付けする

各種センサーを買ったりしたら、こいつを介して操作なり、値取得したいと思ったので、自前でピンをはんだ付けしとく。

f:id:symmt9302:20180101021043j:plain

はんだ付け最中に、カメラのコネクタ的な箇所を折ってしまった様子..........。
まあ、カメラつかわないと思うので、気にしないことにしておく。

f:id:symmt9302:20180101021016j:plain

イメージをSDに書き込む

とりあえず、本家からイメージをとってきます

デスクトップ環境はいらないので、LITEのほうを落としました

f:id:symmt9302:20180101020717p:plain

落としたイメージはEtcherという便利ツールでSDに書き込みます。 OSXでSDのイメージ焼くのダルかった印象あるんですけど、これつかうとサクッといけて最高でした。

セットアップを進める際に、OTGという機能を使ってUSB経由でRaspberry Piの設定をいじりたいので、書き込んだSDの中にある2ファイルを書き換えておく。

cmdline.txt

rootwaitquiet の間に modules-load=dwc2,g_ether を追記する

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=PARTUUID=37665771-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_ether quiet init=/usr/lib/raspi-config/init_resize.sh

config.txt

末尾にdtoverlay=dwc2を追加する

$ echo "dtoverlay=dwc2" >> /Volumes/boot/config.txt

ssh

sshできるようにするために、中身は空で良いのでsshというファイルを作っておく

$ pwd
/Volumes/boot

$ touch ssh

$ ls
COPYING.linux          bcm2708-rpi-0-w.dtb    bcm2708-rpi-cm.dtb     bcm2710-rpi-cm3.dtb    config.txt             fixup_db.dat           kernel.img             ssh                    start_db.elf
LICENCE.broadcom       bcm2708-rpi-b-plus.dtb bcm2709-rpi-2-b.dtb    bootcode.bin           fixup.dat              fixup_x.dat            kernel7.img            start.elf              start_x.elf
LICENSE.oracle         bcm2708-rpi-b.dtb      bcm2710-rpi-3-b.dtb    cmdline.txt            fixup_cd.dat           issue.txt              overlays               start_cd.elf

wifiの設定

wpa_supplicant.confというファイルにWiFiの設定を記述する このファイルを作成しておくと、OS起動時に/etc/wpa_supplicant/wpa_supplicant.confを上書きしてくれるとのこと

country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
network={
    ssid="自分ちのWiFiのSSID"
    psk="対応するPW"
}

ここまでできたら、Zero本体にSDカードをセットし、micro USBデータポートとPCのUSBポートを接続する
(ついでに購入していたケースに収めた)

f:id:symmt9302:20180101020649j:plain

OSXの「共有」の設定画面から「インターネット共有」を選択しRNDIS/Ethernet Gadgetをオンにして、「インターネット共有」をオンにする

f:id:symmt9302:20180101020705p:plain

ここまで一通り終えて何も問題なければ、USB経由でRaspberry Piにアクセスできるようになっている。
raspberrypi.localpingを飛ばすと、疎通が取れることを確認できる。

$ ping raspberrypi.local
PING raspberrypi.local (192.168.2.2): 56 data bytes
64 bytes from 192.168.2.2: icmp_seq=0 ttl=64 time=0.418 ms
64 bytes from 192.168.2.2: icmp_seq=1 ttl=64 time=0.437 ms
64 bytes from 192.168.2.2: icmp_seq=2 ttl=64 time=0.367 ms
64 bytes from 192.168.2.2: icmp_seq=3 ttl=64 time=0.334 ms
64 bytes from 192.168.2.2: icmp_seq=4 ttl=64 time=0.421 ms
64 bytes from 192.168.2.2: icmp_seq=5 ttl=64 time=0.317 ms
^C
--- raspberrypi.local ping statistics ---
6 packets transmitted, 6 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.317/0.382/0.437/0.046 ms

で、疎通が確認できたのでSSHしてみる
が、前のRaspberry Piの設定が残ってて怒られた\(^o^)/

$ ssh pi@raspberrypi.local
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@       WARNING: POSSIBLE DNS SPOOFING DETECTED!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
The ECDSA host key for raspberrypi.local has changed,
and the key for the corresponding IP address 192.168.2.3
is unknown. This could either mean that
DNS SPOOFING is happening or the IP address for the host
and its host key have changed at the same time.
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256:OKg7/purvgRAstmPcBClMoguGxtTq/fYEXgsncrr+uA.
Please contact your system administrator.
Add correct host key in /Users/symmt/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /Users/symmt/.ssh/known_hosts:12
ECDSA host key for raspberrypi.local has changed and you have requested strict checking.
Host key verification failed.

こうやって、以前のキーを無効にしておく

$ ssh-keygen -R raspberrypi.local
# Host raspberrypi.local found: line 12
/Users/symmt/.ssh/known_hosts updated.
Original contents retained as /Users/symmt/.ssh/known_hosts.old

これで、sshすると接続できるはず ifconfigして、WiFiもつながっていることを確認する

$ ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

usb0: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        ether be:00:90:c1:b0:26  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

wlan0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.10.105  netmask 255.255.255.0  broadcast 192.168.10.255
        inet6 fe80::213:71d8:461d:dc5d  prefixlen 64  scopeid 0x20<link>
        ether b8:27:eb:d3:1a:24  txqueuelen 1000  (Ethernet)
        RX packets 408  bytes 63330 (61.8 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 122  bytes 21324 (20.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

Host名を書き換える

ホスト名が他のRaspberry Piと被らないように変更しておく

$ cat /etc/hostname
raspberry-pi-zero

$ cat /etc/hosts
127.0.0.1   localhost
::1     localhost ip6-localhost ip6-loopback
ff02::1     ip6-allnodes
ff02::2     ip6-allrouters

127.0.1.1   raspberry-pi-zero

変更したホスト名でpingできることを確認する

$ ping raspberry-pi-zero.local
PING raspberry-pi-zero.local (192.168.2.7): 56 data bytes
64 bytes from 192.168.2.7: icmp_seq=0 ttl=64 time=0.694 ms
64 bytes from 192.168.2.7: icmp_seq=1 ttl=64 time=0.319 ms
^C
--- raspberry-pi-zero.local ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 0.319/0.506/0.694/0.188 ms

これで一通りの設定はできたと思うので、おしまい そのうち電子工作したらまた書く

参考

マイクプリアンプを作った話

このブログ記事は、 Dark - Developers at Real Kommunity Advent Calendar 20179日目の記事になります。
どちゃくそ遅れていまして、ごめんなさい〜〜〜〜〜〜〜〜〜〜〜。

今日は最近やったマイクプリアンプの話でも雑にしてます。

マイクプリアンプつくった経緯

私は歌うのが好きで、たまに歌ったやつを練習がてらスマホで録音したりするんですよね。
特にどこかに晒すわけでもなく、自分で聴ければいいやってレベルだったので、
スマホについてるマイクで録音をしていました。

そんな私なんですが、最近nanaってサービスを知りました。

めっちゃ面白いじゃん!!!
って思って、スマホで録音したのを投稿してみたりしたんですけど、
使っているスマホのマイクなので、音質が気になり始めたんですね。

で、今はスマホ用のオーディオインターフェースがあったりするので、買ってみました。

TASCAM マイク ギターインターフェース iPad iPhone iPod touch用 iXZ

TASCAM マイク ギターインターフェース iPad iPhone iPod touch用 iXZ

ちなみに、マイクはコレです

BEHRINGER XM8500

BEHRINGER XM8500

これにダイナミックマイクをXLRケーブルで繋いで録音してみたんですが、ボリュームが小さい。
ボリュームをあげるには、マイクプリアンプというボリューム上げるくんが必要になるんですが、
これが買おうとすると安くても5,000円ぐらいするんですね。

既に3,000円つっこんで、更に5,000円突っ込む気にはなれない...。
でも、自作なら2,000円もかからずに作れるのでは?と思って、今回作ってみました。

必要な部品

実際に作るのに必要な部品は以下になりました

必要なもの 金額
プリアンプキット 700円
XLRコネクタ(オス) 324円
XLRコネクタ(メス) 410円
配線 300円
スイッチ 80円
バッテリースナップ 20円
ダイソーに売ってたカードケース 100円

で、合計金額1,934円となり、2,000円いかなかったですね〜。

しかもこの必要なもの一覧、カードケース以外は全部学生の時に買って家に余ってたので、
追加出費したのは100円で済みました。最高。

作り方

やったことはこんな感じ

  1. プリアンプキットの説明書を読みながらはんだ付けする
  2. カードケースに穴をあける
  3. がんばって部品を入れ込む

今回は、マイクプリアンプのキットを買ったので、説明書読んではんだ付けするだけで済みました!
キットをつかって作る分には、プラモデル感覚でモノが作れるので、最高です。

電子工作で一番の難関ぽいのは、個人的にはケースの準備だと思います。
私はよくダイソーにいって調達しています。
最近は、今回も利用しているアクリル製カードケースがめっちゃいいサイズで最高となっております。

ちなみに、穴開けたりするときは、タミヤのリューターを使ってます。
電池式で軽いしまあ便利です。

タミヤ クラフトツール 電動ハンディリューター 74042

タミヤ クラフトツール 電動ハンディリューター 74042

完成形

こんな感じでできました。

f:id:symmt9302:20171224231602j:plain

使うときはこんな感じで、マイクとオーディオインターフェース間にかませます。 ボリューム操作は、オーディオインターフェース側でよしなにします。

f:id:symmt9302:20171224231617j:plain

録音もいい感じの音量でできるようになって、最高でした。

おわりに

こういう電子工作は結構敷居高く感じられる人が多い印象ですけど、
説明書つきのキットなり、作りたいものの回路さえゲットしちゃえば、
難易度的にはプラモデルつくるのと、そう変わりないです。たぶん。

私はオーディオ系のものをつくるのが好きで結構ハマっていました。
マイクとかイヤホンが好きな人は、レシピがよく落ちてるので、つくってみるときっと楽しいです。

最近はRaspberry Piと連携してIoTなこともできますし、
電子工作に触れてみると、プログラミングで遊べる幅も広がって楽しいですよ!
もうすぐ冬休みですし、暇な人はぜひRaspberry PiとLED買ってきてビカビカさせてみたりしてください。笑

Androidで不要そうなPermissionが取得されているので消した話@React Native Advent Calendar 2017

このブログは、React Native Advent Calendar 2017 - Qiitaの7日目の記事です。

私はAndroidがもうそろ1年ぐらいになるエンジニアです。
最近、ReactNativeを触ってアプリを書いていまして、
その際に気になった、AndroidのPermissionの話を書いてみようと思います。

Permissionが取られていることに気づいた

ReactNativeの新規プロジェクトを立ち上げて、
AndroidのmergeされたManifestを見てみると、
以下の3つのPermissionが取られていることに気づきました。

  • WRITE_EXTERNAL_STORAGE
  • READ_PHONE_STATE
  • READ_EXTERNAL_STORAGE

自身のManifestに追加したわけでもないのにな〜〜〜。

あと、以下のPermissionが自分のアプリのManifestに追加されました。

  • SYSTEM_ALERT_WINDOW

「なんだこいつら」
「必要なPermissionなんかコレ」
と調べたところ、いらなそうなんで消しときましょうという話です

なんで謎なPermissionが3つ取られているのか

まずは、以下の3つの権限の話からです

  • WRITE_EXTERNAL_STORAGE
  • READ_PHONE_STATE
  • READ_EXTERNAL_STORAGE

ひとまずReactnativeのリポジトリを見てみたところ、
早速それっぽいIssueを見つけたので読んでみました。

以下はざっくりしたまとめです。

謎にPermissionが取得されている原因

  • 問題の3つの権限は、 org.webkit.android_jsc から取られていた
    • これはAndroid StudioMerged Manifest を見てみるとわかるよ!
  • org.webkit.android_jscAndroid Manifestには、 minSdkVersiontargetSdkVersion が設定されていなかった
  • そのせいで、デフォルト値が適用され、 minSdkVersion=1 targetSdkVersion=1 となっていた
    • 以下はドキュメントからの引用です
      • android:minSdkVersion 警告: この属性を宣言しないと、既定値の「1」と見なされ、アプリがすべてのバージョンの Android と互換性があるものとして扱われます。 アプリにすべてのバージョンとの互換性があるわけではない場合(たとえば、アプリが API レベル 3 で導入された API を使用、など)、適切な minSdkVersion を宣言しないと、API レベルが 3 未満の端末にインストールしたとき、使用できない API にアクセスしようとして実行時にアプリがクラッシュします。 このため、必ず minSdkVersion 属性で適切な API レベルを宣言するようにしてください。 uses-sdk | Android Developers

      • android:targetSdkVersion アプリのターゲットとなる API レベルを指定する整数。設定しない場合、既定値は minSdkVersion で指定した値になります。 uses-sdk | Android Developers

  • minSdkVersiontargetSdkVersion3以下の場合、問題の3つの権限が暗黙的に付与されるようになっていた

解決策

  • 解決方法は、org.webkit.android_jscAndroid Manifestに minSdkVersiontargetSdkVersion を設定すること

以下、かなしいお話

  • 実は、その旨のPRは既に出していて、mergeもされている
  • PRはmergeされているものの、リリースがされていない 😇
  • PRのmerge後に続くコメントを読んだところ、こんなかんじだった
    • FBの人「最初のリリース以降、更新してないからリリースフローがないんすわ 😇」
      • 最初にリリースした人にメンションつけて、どうすればいいんや、と聞く
    • 最初にリリースした人「やる気あったら、あんたリリースしちゃってくれ 😇」
    • そのまま放置.....
  • その後も、FB組に「進捗どう?」的なコメントを投げるも音沙汰なし 😇

対応策

現状、属性マーカー(Attribute Marker)を利用して消すしかなさそう 😇

以下を参考に、実例

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="xxx.xxx.xxx">

  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="remove" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />
  <uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove"/>

というわけで、とりあえずこれで対応することにします 😇

SYSTEM_ALERT_WINDOWは必要なのか...?

次は、SYSTEM_ALERT_WINDOWの話です。
この権限は、WarningやCrashをデバッグ時に画面に表示するために取られていたPermissionのようです。
つまり、デバッグビルドのときは欲しいけど、リリースビルドでは特に必要のないものです。
消してしまいましょう!!!!

以下を参考に、実例

app/src/release/AndroidManifest.xmlにRelease Build用のmanifestをつくる 中身は以下のような感じ

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >

    <uses-permission tools:node="remove" android:name="android.permission.SYSTEM_ALERT_WINDOW" />
</manifest>

これで、リリース時にはSYSTEM_ALERT_WINDOWのPermission消せる

まとめ

ReactNativeで最初から付与されているPermission群の削除は、
app/src/main/AndroidManifest.xmlを以下のようにすると消せるよ

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="xxx.xxx.xxx">

  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="remove" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove" />
  <uses-permission android:name="android.permission.READ_PHONE_STATE" tools:node="remove" />
  <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

  ...

</manifest>

SYSTEM_ALERT_WINDOWのPermissionは、デバッグ時以外は不要なので、
以下の内容でapp/src/release/AndroidManifest.xmlにRelease Build用のManifestをつくるといいよ

<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    >

    <uses-permission tools:node="remove" android:name="android.permission.SYSTEM_ALERT_WINDOW" />
</manifest>

低温調理機をつくったお話 @低温調理 Advent Calendar 2017

この記事は、低温調理 Advent Calendar 2017の3日目の記事です。
盛大に遅刻しました。ごめんなさい。

低温調理機をつくった話をします

低温調理機は常々ほしいと思っていたのですが、そこそこ値段するんですよね。
それっぽいものつくれないかなと思って調べたら、イケそうな気がしたのでつくりました。
7月くらいには、勉強会で発表ネタにもしてみてみました。

今回は、その時の発表内容を文字起こししていく感じでやっていきます。

低温調理機の構成

実装のために、低温調理機の構成を考えてみます。
以下の4要素あればイケる気がしました。

  • コンピュータ
    • 温度を見て、水温を一定に保つ
  • ヒーター
    • 水温を上げる
  • 温度計
    • 水温を測る
  • 撹拌機
    • 水温を全体的に均一にする

で、実装にあたって今回はこんな感じでやりました

  • コンピュータ
  • ヒーター
    • 水槽用ヒータ
  • 温度計
    • 1-wireでデータを取れる水温計
  • 撹拌機
    • 水槽用ポンプ

これらを購入してきて、実際に低温調理機をつくってみました。

実装(物理)

回路図等飛ばして、完成写真はこんな感じになります。

f:id:symmt9302:20171204010436j:plain

実際に低温調理機をつくるのに、だいたい以下のものを買いました。

物品 価格 備考
Raspberry Pi 600円ぐらい~5,000円ぐらい 温度制御用(私は初代を持ってたのでそれつかった)
水槽用ヒータ 1,680円@amazon 水を温める
ソリッドステートリレー 800円@秋月 ヒータのオンオフ制御用
ヒューズ 30円@秋月 事故防止用。5Aでよかったかは謎。
延長コード 100円 途中にヒューズをかませて、電源とヒータの間につかう
水温計 263円@amazon 水温測るのにつかった
水槽用ポンプ 677円@amazon 水を撹拌するのにつかう
バケツ 300円 水槽つくる用

私はRaspberry Piを元々持っていたので、それを抜くと4,000円ぐらいで作れました。

雑に説明すると、こんな構成になっています。

  • 延長コードにヒューズとソリッドステートリレーをかませ、水槽用ヒーターをつなぐ
  • バケツに水を張り、ポンプを設置する
  • ソリッドステートリレーと水温計をRaspberry Piにつないでおく

物理層自体はそんなもんで、
あとは温度制御をするためのプログラムを準備すると調理機になってくれます。

実装(コード)

実際のコードは以下に公開してます。

Symfony3(PHP)で書いてあって、
設定した温度に合わせて、ヒーターをON/OFFして温度制御してくれます。
PHPが書ければ、肉を調理することができる便利な時代になりました。

こんな感じでAPIを叩くと調理が始まる感じになっています。

$ curl -sS -X POST http://127.0.0.1:8000/cooking/status
{"message":"The cooking does not begin yet."}

$ curl -sS -X POST http://127.0.0.1:8000/cooking/start --data 'cookingTemperature=56' --data 'cookingTime=09:00:00' --data 'description=ローストビーフ'
{"isCooking":true,"CookingTime":"09:00:00","CookingTemperature":56,"CurrentTemperature":"55.562","CookingStartTime":"2017-06-26 23:38:04","CookingEndTime":"2017-06-27 08:38:04","description":"\u30ed\u30fc\u30b9\u30c8\u30d3\u30fc\u30d5"}%

$ curl -sS -X POST http://127.0.0.1:8000/cooking/status | jq .
{
  "isCooking": true,
  "CookingTime": "09:00:00",
  "CookingTemperature": 56,
  "CurrentTemperature": "55.375",
  "CookingStartTime": "2017-06-26 23:38:04",
  "CookingEndTime": "2017-06-27 08:38:04",
  "description": "ローストビーフ"
}

APIはここらへんに書いてあります。

実際の温度制御はここらへんに書いてあります。
PI制御するようにしていて、実装は@hamanoさんのブログを参考にしました。

雑ですが、そんな感じで低温調理機が完成しました。

補足:温度計の校正

調理機自体はできたのですが、温度計の校正をしとくと、なお良いです。
使っているセンサーの温度をそのまま流用すると、誤差があったりすることがあります。
なので、稼働前に信頼できそうな温度計を準備して、
購入したセンサーで取得した温度と差分がないかをチェックしておくといいと思います。

簡単な方法としては、沸騰したお湯にセンサーをつっこんで、
100度からどれくらい差分があるかを確認する方法かと思います。
差分があったら、その分プラマイしておくと、正確そうな温度がとれる気がします。

自分の書いたプログラムだと、
Serviceのパラメータに補正値を登録しといて、温度取得の際に補正してます。

テスト稼働

完成して、めでたいので、肉をアレしてみましょう。

豚肩ロースを買ってきました。

f:id:symmt9302:20171204010839j:plain

これを、ニンニクとか黒胡椒とかを入れた塩分濃度1%ぐらいの塩水に1晩つけときます。
人間は塩分濃度0.8%ぐらいが丁度いい塩加減と感じるそうです。
なので、それに近そうな濃度にしときます。

f:id:symmt9302:20171204010312j:plain

調理機に突っ込みます。
63℃で3時間ぐらいやっときました。

f:id:symmt9302:20171204012009j:plain

取り出して切ってみた様子です。
ちょっとギッチギチに詰めすぎたようで、場所によって火の通りが違いました。
つっこみすぎに気を付けましょう。
左から右に行くほど、火が入っていて、肉が縮んでパサついております。
私的には、一番左の肉塊がいい感じでした。

f:id:symmt9302:20171204010539j:plain

焼き色を付けるといい匂いがして美味しく感じるものなので、
煙出るほど熱した鉄フライパンでジュッと焼いておきます。

f:id:symmt9302:20171204010541j:plain

そんなわけで、美味しく調理できました。
完成写真については、写真を撮り忘れていて無いです\(^o^)/←

とりあえず、美味しかったので、成功です。
豚肩ロースは結構美味しくできるのでおすすめです。
わさびつけて食べると美味しいです。

おわりに

ぶっちゃけ、買ったほうがラクですけど、自分で調理機つくるのは楽しかったです。
Raspberry Piとやっていきな気持ちがあれば作れるので、ぜひやってみてください。

おまけ: 風呂ーストビーフをつくる

低温調理してみたいけど、こんなにいろいろやるのはダルい方に朗報です。
ご自宅の給湯器で温度を 75℃程度まであげれれば、お風呂で低温調理ができます!

やり方は簡単です。

  1. 給湯器の温度を75度ぐらいに設定する

f:id:symmt9302:20171205002011p:plain

  1. お風呂にお湯を張る
  2. 密封した肉を風呂に放り込む
    • 水温的に、牛ももがいい感じです

f:id:symmt9302:20171205002017j:plain

  1. 3時間程度放置する
  2. 回収して、焼き色をつけてカットして完成

f:id:symmt9302:20171205002023j:plain

この日はローストビーフ丼にしました。

f:id:symmt9302:20171205002057j:plain

光景がなかなかフォトジェニックですが、お肉は美味しくできました。
お風呂に入れないぐらいのお湯を張ったときにでもやってみてください。

参考にしたものリンク

ExoPlayerでMediaSessionをいい感じに扱う

概要

ExoPlayerを使って音楽再生しようとしてた
んで、MediaSessionを使っていき〜〜〜〜というふうになっていた

「色々書いてくの、めんどくさいな〜〜〜〜」と思っていたところ、そこらへんをよしなにしてくれるextensionがあると知った

他のブログとか特に見つからず、公式ブログを読んでも、パッと理解できなかったので、動作確認できたコードを残しておく

ExoPlayerのExtensionを使っていいかんじにやっていく

例にあげたコードは以下を元にしています

Before

public class MediaPlaybackService extends MediaBrowserServiceCompat {
    private static final String MY_MEDIA_ROOT_ID = "media_root_id";
    private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id";

    private MediaSessionCompat mMediaSession;
    private PlaybackStateCompat.Builder mStateBuilder;

    @Override
    public void onCreate() {
        super.onCreate();

        // Create a MediaSessionCompat
        mMediaSession = new MediaSessionCompat(context, LOG_TAG);

        // Enable callbacks from MediaButtons and TransportControls
        mMediaSession.setFlags(
              MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
              MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

        // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
        mStateBuilder = new PlaybackStateCompat.Builder()
                            .setActions(
                                PlaybackStateCompat.ACTION_PLAY |
                                PlaybackStateCompat.ACTION_PLAY_PAUSE);
        mMediaSession.setPlaybackState(mStateBuilder.build());

        // MySessionCallback() has methods that handle callbacks from a media controller
        mMediaSession.setCallback(new MySessionCallback());

        // Set the session's token so that client activities can communicate with it.
        setSessionToken(mMediaSession.getSessionToken());
        
        // MediaSession to active.
        mMediaSession.setActive(true);
    }
    
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        MediaButtonReceiver.handleIntent(mMediaSession, intent);

        return START_NOT_STICKY;
    }
}

After

こんな感じでPlaybackStateCompat.BuilderMySessionCallbackがいらなくなる

public class MediaPlaybackService extends MediaBrowserServiceCompat {
    private static final String MY_MEDIA_ROOT_ID = "media_root_id";
    private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id";

    private MediaSessionCompat mMediaSession;
    private PlaybackStateCompat.Builder mStateBuilder;
    private SimpleExoPlayer mPlayer;

    @Override
    public void onCreate() {
        super.onCreate();

        mPlayer = ExoPlayerFactory.newSimpleInstance(this, new DefaultTrackSelector());

        setBroadcastReceiver();
        mAudioPlayer = new AudioPlayer(this);
        mAudioPlayerNotification = new AudioPlayerNotification(this);

        // Create a MediaSessionCompat
        mMediaSession = new MediaSessionCompat(this, LOG_TAG);

        // Enable callbacks from MediaButtons and TransportControls
        mMediaSession.setFlags(
                MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
                MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);

        // Set the session's token so that client activities can communicate with it.
        setSessionToken(mMediaSession.getSessionToken());

        // Used MediaSession extension
        MediaSessionConnector mediaSessionConnector = new MediaSessionConnector(mMediaSession);
        mediaSessionConnector.setPlayer(mPlayer, null);
        
        // MediaSession to active.
        mMediaSession.setActive(true);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        MediaButtonReceiver.handleIntent(mMediaSession, intent);

        return START_NOT_STICKY;
    }
}

補足

再生/一時停止再生一時停止秒送り をしたいときは、PlaybackControllerを自前で実装して、

mediaSessionConnector = MediaSessionConnector(MediaSessionCompat mediaSession,PlaybackController playbackController)

すればよさそう

曲送りやら、ランダム再生やらをしたいときは、QueueNavigatorを実装して

mediaSessionConnector.setQueueNavigator(QueueNavigator queueNavigator)

とすればよさそう

おわりに

ExoPlayerのドキュメント、超読みにくかったり、欲しい情報どこにあるのか謎な問題、あるとおもいます

ReactNativeのアプリをバックグラウンドにするとJSのコードが動作しない(Android)

ReactNativeで書いたコードが、アプリをバックグラウンドにした時、動作しないと気づいた。

ネイティブコードで非同期処理をして、その結果を こんなかんじで JS側にイベント投げて、stateの更新を行うみたいなことをしていたときに、アプリがバックグラウンドになってるとイベントのキャッチができてなくて、stateが更新されず、はあ...?となっていた。

テキトーなコードを書いてみたところ、バックグラウンド時にはJSのコードが動いていない様子だった(0.44.0と0.49.3で確認した)。

例えば、以下のようなコード。
1秒ごとに「wei」とログを吐き続けるウェイなアプリを作ったとする。
フォアグラウンドのときは動作するが、バックグラウンドにしたあと、ウェイしてくれなくなる...。

App.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  Text,
} from 'react-native';

export default class App extends Component<{}> {
  componentWillMount() {
    console.log('App.js: componentWillMount()');

    setInterval(
    () => { console.log('App.js: wei') }
    , 1000);
  }

  render() {
    console.log('App.js: render()');
    return (
      <Text>Wei</Text>
    );
  }

  componentDidMount() {
    console.log('App.js: componentDidMount()');
  }

  componentWillUnmount() {
    console.log('App.js: componentWillUnmount()');
  }
}

index.js

import { AppRegistry } from 'react-native';
import App from './App';

AppRegistry.registerComponent('CheckBackground', () => App);

以下がその時のログ

バックグラウンドにするとウェイウェイしてくれなくなった

// フォアグラウンドのとき

10-22 22:31:41.910 30529-30578/com.checkbackground I/ReactNativeJS: App.js: componentWillMount()
10-22 22:31:41.914 30529-30578/com.checkbackground I/ReactNativeJS: App.js: render()
10-22 22:31:41.948 30529-30578/com.checkbackground I/ReactNativeJS: App.js: componentDidMount()
10-22 22:31:42.925 30529-30578/com.checkbackground I/ReactNativeJS: App.js: wei
10-22 22:31:43.940 30529-30578/com.checkbackground I/ReactNativeJS: App.js: wei
10-22 22:31:45.031 30529-30578/com.checkbackground I/ReactNativeJS: App.js: wei
10-22 22:31:45.975 30529-30578/com.checkbackground I/ReactNativeJS: App.js: wei
10-22 22:31:46.989 30529-30578/com.checkbackground I/ReactNativeJS: App.js: wei
10-22 22:31:48.010 30529-30578/com.checkbackground I/ReactNativeJS: App.js: wei
10-22 22:31:49.031 30529-30578/com.checkbackground I/ReactNativeJS: App.js: wei

// ここでバックグラウンドにした ウェイしてくれなくなる

ドキュメント等できちんとしたエビデンスが得られていないが、バックグラウンド時には、JSコードは動作しなくなる的なコメントがStackOverFlowにあった でもこれ2年前近いコメントなんだよな...。挙動的に今もそうなってるぽいけど...。

There is a limitation in React Native wherein when the app is in the background the js bridge stops getting messages. This means if you are trying to send the data from js then you won't be able to count on the data making it. Alternatively you could write the code to send the updates in native code and that should do the trick javascript - How can I run background tasks in React Native? - Stack Overflow

認識が間違ってたり、ドキュメント等をご存知でしたら、コメントで教えてもらえると泣いて喜びます

staticフィールドは、意図せず初期化されることがある

概要

  • AndroidJavaで書くマンしている
  • 今まで、staticに突っ込んだ値は、アプリのプロセスが終わるまでは生きていると思っていた
  • プロセスが終わるまで生きてるんだから、staticに値つっこんで管理せばええやろ〜〜〜と思って、そういったコードを書いている箇所があった
    • 今回の場合、staticフィールドに状態を持たせるようなコードを書いていた
  • 「staticの値って、ホントにプロセス終わるまで生きてるの?」「その理解、ホント?」とレビューで突っ込まれた
  • 先に書いていた理解してた事柄は、学生の時に「そうなんだ〜。ふ〜ん。」程度に覚えていた知識で自信もなかったので、調べたのでそのメモ

先に結論

  • staticも初期化される可能性は普通にある
  • staticとするのは定数のみにして、状態管理に使わないほうが良さそう

(この理解に間違いがあれば、何かしらでツッコミを入れてもらえると、とてもありがたいです)

Staticな変数ってそもそもなに

インスタンスではなく、クラスに紐付けられた変数
インスタンス変数 ↔ クラス変数

JVMの概要

じゃ、そのクラスの情報はどう管理されているか。 JVMがどうなっているか調べてた。

JVM | Java Virtual Machine - Javatpoint

ざっくり

  1. JVMが起動すると、ClassLoaderなるものが動作し、アプリケーションで利用するクラスを読み込む
  2. 読み込んだ内容は、メモリに展開され、画像中央の白枠のようにそれぞれ領域別に展開される
    • アプリケーションに含まれているクラスの中身は、Class Areaに読み込まれる
      • staticな変数(クラス変数)はこの領域に格納される
    • 生成したインスタンスHeapにつっこまれる

クラスローダーとかいうやつが、鍵っぽい

クラスローダー

JVM起動時にアプリケーションで利用するクラスを読み込んで、メモリに展開するくん

クラスローダにはいくつか種類がある

  • ブートストラップ・クラス・ローダー

    • 起動時にまず呼び出されるクラスローダ
    • JVM自体の実行に必要となるシステム・クラスをロードするのが役割
    • 注意として、読み込めるすべてのクラスをロードするのではなく、アプリケーションからのクラス参照があった時にロードされる
    • JDKディストリビューションによって提供されているすべてのクラスがこのクラス・ローダーによってロードされると理解して問題ないとのこと
    • オプション-Xbootclasspathをいじると、ブートストラップ・クラス・ローダーがロードできるクラス・セットの範囲を変更できる
  • 拡張クラス・ローダー

    • ブートストラップ・クラス・ローダの子にあたる
    • 最初に親に委譲し、その後必要なら、そこではじめて対象のクラスを検索し、解決する
    • 拡張ディレクトリという領域からクラスをロードする
      • これは、java.ext.dirsシステム・プロパティで指定する
    • ロードされたクラスは、JVM固有の構成を指定するために使用でき、JVMプロセスに追加でロードするライブラリを変更できる
  • システム・クラス・ローダー

    • アプリケーション・クラスおよびクラスパス上にあるクラスをロードする
    • 拡張クラス・ローダーと同様に、最初に親に委譲し、その後必要なら、そこではじめて対象のクラスを検索し、解決する
    • オプション-cpでクラスパスを指定できる

ロードについて

大きく4つのフローがある static変数に値を突っ込むのは、準備の段階らしい

  • 検証
    • クラスが破損していないこと、および構造的に正しいことを検証する
    • 実行時コンスタント・プールが妥当
    • 変数の型が正しいこと
    • 変数がアクセスされる前に初期化されている
  • 準備
    • 静的フィールドをそれぞれの型に合うデフォルト値に初期化する処理が含まれる
    • 例:準備後はint型のフィールドには0、参照はnullになる
  • 解決
    • 実行時コンスタント・プール内のシンボリック参照が、実際に必要とされる型の妥当なクラスを指し示していることをチェックする
    • シンボリック参照の解決が契機となり、参照先のクラスのロードが行われる
  • 初期化子群の実行
    • クラスが準備済み、検証済みであることを前提として、クラスの初期化子(イニシャライザ)を実行する
    • すべての静的初期化ブロックのコードを結合した静的初期化子(スタティック・イニシャライザ)メソッドも実行される
      • 初期化プロセスは、ロードされたクラスごとに1回のみ実行すべきものであり、同期的に処理される
      • これは特に、クラスの初期化によって他のクラスの初期化が起動される恐れがあるため、デッドロックに注意して初期化を行う必要があるから
        • 初期化処理が重複して行われないようにしたいから、という理解でいいんだろかな

アンロードについて

ロードはJVMの起動時に行われ、不要となったクラスのアンロードは、そのクラスが利用されなくなったときに行われる
これが実行されるとクラスのデータも破棄されるので、staticフィールドも破棄される
どういったときにアンロードされるのかは、以下のとおりらしい。

クラスのアンロードは、そのクラスが不要になった時に起こる。 クラスが不要になる条件は、以下の 3 つの条件を全て満たす必要がある。

  1. ヒープ中からそのクラスのインスタンスがなくなること。
  2. そのクラスの static メソッドを実行中のスレッドがいないこと。
  3. そのクラスをロードしたクラスローダーを現わす ClassLoader 派生型のインスタンスがヒープ中からなくなること。

クラスが 1. ~ 3. の条件を満たしているかどうかの判断は、実装的な理由により GC 時に行われる Java VM が多い。

Java のクラスアンロード (Class Unloading)

アンロードが起こるとstaticフィールドの値も吹き飛ぶことがわかった。

でもそれ、AndroidJVMだと、どれぐらいの頻度でおこるもんなの...?
公式には以下のように書いてあった
先の1~3と同じ条件と思っていれば良さそうな気がした

The class references, field IDs, and method IDs are guaranteed valid until the class is unloaded. Classes are only unloaded if all classes associated with a ClassLoader can be garbage collected, which is rare but will not be impossible in Android. Note however that the jclass is a class reference and must be protected with a call to NewGlobalRef (see the next section).

JNI Tips | Android Developers

ただ、DalvikとARTどっちもそういう理解でいいのか...?

Dalvikについては、以下のStack OverflowにDalvikのエンジニアやってた人がコメントしている
先の1~3と同じ条件と思っていれば良さそうな気がした

ARTについては、それらしい文献を見つけれていない
明示的にARTって書いていないけど、公式にアンロードされることがあるって書いてあるし、そう思っておくことにする

また、以下の記事があった
公式ドキュメントでは「あんまりアンロード起こらんよ」とはいいつつ、わりと普通にunloadされているらしい

ご存知の通り、Androidはメモリが逼迫するとバックグラウンドにあるActivityやらServiceやらを殺していく。 殺されるのはそういったAndroid特有のオブジェクトだけではなく、 アプリのライフサイクルとは無縁の上記ContextHolderのようなオブジェクトも例外では無い…ように感じる。 というのも、実際に上記のようにstaticで参照を保持しているフィールドにアクセスするアプリで、 ぬるぽでクラッシュしたレポートがコンスタントに上がってきているので。 クラスがUnloadされると、次回必要になった際に再Loadされる。 その時に初期値を設定していないstaticフィールドはnullで初期化される。 そこで本来はApplicationクラスのonCreate()でContextを設定したいところが、 プロセスまでは殺されていないためにApplicationクラスのonCreate()が再度呼ばれず、 staticフィールドはnullのままになってしまうのである。

Androidアプリでstaticフィールドは絶対ではない - PEEE802.11

結論

  • staticも初期化される可能性は普通にある
  • staticとするのは定数のみにして、状態管理に使わないほうが良さそう

(この理解に間違いがあれば、何かしらでツッコミを入れてもらえると、とてもありがたいです)

参考