帰宅してから急激に体調が悪化し、悪寒がひどくて眠れないぐらいに。体温を計る余裕がなかったけど、38度〜 ぐらいは出ていた気がする。1時間ぐらい悪寒と戦ったあと、どうしよもないので家で余っていたロキソニンを飲んだら急激に楽になって寝れた。朝には熱はひいたが頭痛がひどかったので病院に。

熱の原因はよくわからなかった。ここ数週間地味に頭痛があったり体調が思わしくないけど、とりあえず緊張性頭痛だろうと言われた。熱にも頭痛にも効くからと葛根湯が処方された。

16:40〜 自作キーボードの話を1時間ぐらいする予定です。なんか1日の最後なのでゆるめでやりたいという気持ちはあります。

おおむね、事前のトーク概要と変わらない内容で話せそうです。

資料つくってます…… 話がまとまらない……

ref. builderscon - Discover Something New

  1. トップ
  2. tech
  3. builderscon tokyo 2016 でしゃべります

ありがとうございました。

聞いたトークで印象に残ったもの

OSS は Windows で動いてこそ楽しい - builderscon tokyo 2016

mattn さんが思ったより全くオッサン感なくてビビった。。。挨拶しようと思ったけどできなかったので、いつか(いつ?)挨拶したい。発表もおもしろかった。

動け!Golang 〜圧倒的IoTツール開発へようこそ〜 - builderscon tokyo 2016

IoTツール開発は生産管理ツールについてで、ものすごく貴重な話だと思った。懇親会でももうちょっと聞いたりした。

The Open Beer Server - theory and the implementation - builderscon tokyo 2016

今回のゲラゲラ枠っぽいが、ものすごい金かかっててやばかった……

C 言語で行う Web フロントエンドプログラミング - builderscon tokyo 2016

Emscripten の話とか。ブラウザって VR もサポートする予定なのか!と思った。あと Emscripten が頑張って OpenCL を WebGL に変換するとか知らなかった。

「片手間JavaScripter」にも知ってほしい、Vue.jsで実現するMVVMパターン、Fluxアーキテクチャとの距離 - builderscon tokyo 2016

Flux 触ったことなかったのでわかりやすかった。

そろそろプログラマーもFPGAを触ってみよう! - builderscon tokyo 2016

FPGA の話とても聞きやすくて解りやすくてよかった。

一から始めるJavaScriptユニットテスト - builderscon tokyo 2016

JS のテスト、闇雲にググっても萎えるだけなので、こういうまとまった資料ってほんとまじで大事

  1. トップ
  2. tech
  3. builderscon tokyo 2016 で「 Bluetooth キーボードの作りかた」を喋りました

いままでハマったことがなかったのだけど、ついにハマってしまった。
補助グループ権限もつけてくれるsetuidgidのようなもの - (ひ)メモ を読んで、どうするかな〜と思いつつsetusergroupsを試してみたが、Unix::Groups がデフォルトで入っておらずちょっと困ったので python で以下のようにした。

#!/usr/bin/python

import os
import sys
import pwd

if len(sys.argv) < 3:
    print >> sys.stderr, 'Usage: setusergroups user program'
    sys.exit(1)


pw = pwd.getpwnam(sys.argv[1])
pw_name = pw[0]
pw_uid = pw[2]
pw_gid = pw[3]
pw_home = pw[5]

os.environ['HOME'] = pw_home
os.initgroups(pw_name, pw_gid)
os.setgid(pw_gid)
os.setuid(pw_uid)
os.execvp(sys.argv[2], sys.argv[2:])

エラると例外で落ちてトレースバックがでて十分なので特にエラー処理してな

備考

ハマったのは Raspberry Pi の pi ユーザで i2c を触るようなデーモンを作るケースで、この場合 pi ユーザに i2c グループ権限がついてないとダメなのでこういうのが必要になる。

依存なしでどうにかしたかったのは、cpanm が ansible の Core Modules に入ってなくてめんどかったから

  1. トップ
  2. tech
  3. daemontoolsのsetuidgidが補助グループ (supplementary groups) 権限をつけてくれない問題 (python)

前まで webp のアップロードはできなかった気がするんだけど、最近試したらできるようになっていた。

Google Photos はいつからかブラウザによって webp が落ちてくるように変わったけど、そのタイミングでアップロードも変わったのかな?

ちなみに JPEG 2000 や JPEG XR はあいかわらずアップロード不可能

  1. トップ
  2. tech
  3. Google Photos の webp 対応

さくらのVPSにMongo Rescueのリストアができなかった (未解決) | tech - 氾濫原 の続きで、結局クリーンインストールした。さっき DNS を切り替えたので様子見。一応 DNS の TTL 時間はとっくにすぎたので、変な BOT 以外はいまのところ旧サーバにアクセスない。

どうやったかのメモはまた別にかく


一通り移行できたつもりだけど、元々雑多なサーバ内容かつ長期運用していたのでよくわからない。外形監視を自宅 Raspi からやっておくべきだったかなあ。

プランは東京 2GB SSD にした。前がだいぶ古い大阪 1GB HDD プランだったので SSH で繋いで体感できるぐらいパフォーマンスあがった。ウェブ側も早くなってて Google からの評価があがるといいんだけどな〜

  1. トップ
  2. tech
  3. サーバー移行

カスタムOSインストールガイド - Ubuntu 16.04 – さくらのサポート情報 を見ながらインストールまでやる。

旧サーバと同じホスト名にしてDNSをふりかえたいが、DNS やホスト名まわりは最後にやる。

SSH の設定

手元からまず copy-id

$ ssh-copy-id user@ufhostname

リモートの設定をかえる

$ sudo vi /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
UsePAM no
$ sudo /etc/init.d/ssh restart
# 別のターミナルを開いて ssh できることを再度確認

Firewall の設定

ssh, http, https と mosh 用の udp ポートをあけておく (該当するソフトウェアをインストールすると勝手に空いたりするのだが念のため)

sudo ufw default deny
sudo ufw allow 80
sudo ufw allow 443
sudo ufw allow 22
sudo ufw allow 60000:61000/udp

sudo ufw enable

必要なパッケージいれる

sudo apt-get install zsh gcc libncurses5-dev language-pack-ja git daemontools daemontools-run mysql-server ca-certificates cpio curl dnsutils imagemagick imagemagick-common irssi libcairo2-dev libcurl4-openssl-dev mime-support   nginx nginx-common nginx-full ntp ntpdate optipng pkg-config  rsync shared-mime-info w3m wget xz-utils xml-core zip libxml2-dev libtiff5-dev libssl-dev libsqlite3-dev libreadline-dev libpng12-dev  libpango1.0-dev libopenjpeg-dev libmysqlclient-dev libjpeg8-dev libgif-dev libfreetype6-dev libffi-dev cmake screen ruby-dev rrdtool libdb-dev postfix unattended-upgrades
sudo ln -s /etc/service /service

旧サーバから rsync

home ディレクトリごとまるごと移す

一時的に sudo で rsync をつかえるようにする

$ sudo vi /etc/sudoers.d/rsync
Defaults    env_keep += "SSH_AUTH_SOCK"
Defaults!/usr/bin/rsync    !requiretty
cho45    ALL=(ALL)       NOPASSWD: /usr/bin/rsync
 ||<

>||
## dry-run 
sudo rsync --dry-run -auvz -e 'ssh -i /home/cho45/.ssh/id_ecdsa' --rsync-path='sudo rsync'  --progress  /home/cho45/ cho45@160.16.210.51:/home/cho45/

ほかもうつす

/srv
/etc/nginx
/etc/postfix
# /etc/mysql 変更点がなかった。。。あらためて見直そう
/etc/letsencrypt
# /var/lib/mysql mysqldump でやった
# /service 手動でやる

意外とうつすのない?

h2o のインストール

git の HEAD を入れてるので再度コンパイルして /usr/local にインストール

cd ~/project/h2o
git clean -f
cmake -DWITH_BUNDLED_SSL=on .
make
sudo make install

ログファイルを /var/log/h2o に作るようにしてるが前もってディレクトリがないとしぬ

sudo mkdir /var/log/h2o

/etc/logrotate.d/h2o の設定

/var/log/h2o/*.log {
    daily
    missingok
    rotate 90
    compress
    delaycompress
    notifempty
    create 0640 www-data adm 
    sharedscripts
    postrotate
        svc -h /service/h2o
    endscript
}

perl のインストールしなおし

5.14.4 が gcc の関係?で入らなかったので、これを期に 5.22.2 に移行

perlbrew install perl-5.22.2 --as perl-5.22

前のマシンで perlbrew list-modules した結果を保存して cpanm にくわせてインストール。入らないやつが無視されるので、完全ではないがだいたい入る。

5.22 で動かなくなったコードを地味に修正したりした。

mysql

db ファイルだけ rsync したらいけるだろと思ったけど割といけなかったので、dump して restore する。mysql 自体のバージョンもあがってるのでまぁこのほうがよさそう。

mysqldump -uroot --routines --all-databases --flush-privileges | ssh cho45@160.16.210.51 'mysql -uroot'

たいしてデータ入ってないので一瞬。--routines でユーザテーブルもコピーしている。 --flush-privileges をつけないとコピーしたユーザデータが反映されないのでつけとく。

動作確認

160.16.210.51 www.lowreal.net
160.16.210.51 lowreal.net
160.16.210.51 cho45.stfuawsc.com
160.16.210.51 coqso.lab.lowreal.net
160.16.210.51 no-real.net
160.16.210.51 block.lab.lowreal.net

みたいなのを /etc/hosts に書いて検証。証明書も転送して設定済みなので DNS だけ書きかえればうごいてくれるという感じのはず。。

DNS 変更

DNS の向き先を新サーバに向ける

ホスト名と逆引きを設定

さくらのVPSはA レコードがひけてないと逆引き設定がつくれないので、Aレコードが適用されたあとにやる。

/etc/hostname も変える。

ここまでやったら一旦リブート。30秒ぐらいで起動するのでたすかる……

cron の再設定

旧サーバからぬいてくる

crontab -l > crontab.user
sudo crontab -l > crontab.root

新サーバに適用する

crontab crontab.user
sudo crontab crontab.root

カーネルパラメータ

/etc/sysctl.conf に追記

net.core.somaxconn = 50000
net.ipv4.tcp_max_syn_backlog = 30000
net.core.netdev_max_backlog = 5000

net.ipv4.ip_local_port_range= 1024 65535

sudo sysctl -p で反映

ref

  1. トップ
  2. tech
  3. Ubuntu 16.4 LTS クリーンインストールして引越

 -

5.0 / 5.0

これだけ買った。本当はおむつが欲しいんだけど、欲しいブランドのものがなかった…

定期便との併用

定期便でも頼んでるけど、定期便で頼んでいると在庫確認に無頓着になりがちで

  • 届きすぎる
  • いつのまにかなくなってる

の二択になる。適切な配達間隔を設定すること自体が難しい。定期便自体が月イチの決まった日付前後の配達になるので「なくなりそうだから頼もう」と思ったタイミングだと時既に遅しということがある。

結局

  • 常に多めに頼んで、ときどき配送をキャンセル
    • 自宅にストックが溜るので邪魔
    • 結構頻繁に定期便の設定変更しててめんどい
    • 途中で必要なくなったら余計な分がまるまる無駄
  • 常に少なめに頼んで、ときどき追加で発送
    • 追加分は定期便割引をうけられない

のいずれかになる。


おむつの場合ストックがものすごい邪魔なので、多めに頼むってのが難しい。それと成長にともなって商品変更を迫られるので、そもそも大量に買いたくない。おかげで、なくなりそうになったらプライムで追加発送とか、近くのドラッグストアで買うみたいな運用になる。

Dash Button だと「足りない」と気付いたときにすぐ買うという場合は便利そう。そして物理ボタンなので家族で共有して押せるというところが便利。洗剤とかだと気付いた人が詰めかえるという運用なので「なくなったから買って」「忘れてた」「はやく買って」「カートにはいれた」みたいなことが発生してめんどうくさい。

ただし「もうすぐ定期便で届くし、それまでは在庫持つよ」というケースでも発注してしまうことが予想できてちょっとむずい。

キャッシュの持ちかたを変えて、圧縮 (gzip) して持つように変えた。キャッシュを返すときは gzip ずみのをそのまま返すように。

gzip 非対応ブラウザでおかしくなる気がするけど、昨今そんなブラウザは聞いたことがないので現実的には問題なかろうという気がする。

これによりキャッシュ用のDBファイル(SQLite)のサイズが劇的に減ったので、ファイルキャッシュに乗りやすくなったはず……

  1. トップ
  2. tech
  3. このサイトのキャッシュ

誤り訂正や、超解像技術みたいなのを前提とした圧縮方法に興味がある。なんとなく面白い感じがする。本来存在していたデータを意図的に削って送って、受取側の「経験」みたいなものを信用する感じが面白い。

画像圧縮も、CPU速度と帯域を比べたときに帯域で律速されるぐらいに高解像度になると、ディープラーニングによる高解像度化や、自動色付けを前提とした画像圧縮アルゴリズムが産まれても不思議ではない。

つまりこれは「おい」と言うとお茶がでてくるみたいなシステム。これがコンピュータで実現すると面白い。送信側の「意図」みたいなものを最大限推論してデータを復元し、転送データを最小限にできる。

  1. トップ
  2. tech
  3. 「おい」と言うとお茶がでてくるみたいな圧縮アルゴリズム

最初からアドレスついてたので意外とやることない。

ifconfig すると既に v6 のアドレスがついている。Scope:Global になっているやつがグローバルIPアドレス (正確にはグローバルユニキャストアドレス)

inet6 addr: 2001:e42:102:1807:160:16:210:51/64 Scope:Global

これを該当ドメインの AAAA レコードでひけるようにする。

nginx

nginx -V の結果に --with-ipv6 があることを確認する。

server {
    listen   80; 
    listen [::]:80;
    ...
}

という感じにして ipv6 側もlistenするようにする。

h2o

listen: 80 と書いてる場合、デフォルトで v6 も listen するようになっているのでやることはない。

DNS サーバの IPv6 対応

ここでの「DNS サーバの IPv6 対応」はDNS サーバへの通信が IPv6 でできるかという話になるが、value-domain の NS1.VALUE-DOMAIN.COM とか、さくらインターネットの ns1.dns.ne.jp は対応していないっぽい?

IPv4 で解決できるなら別に問題ないんだけど、IPv6 オンリーの環境 (あるの?) からだと名前がひけないことになる。

確認

http://ipv6-test.com/validate.php これが便利

備考

自分に IPv6 アクセス環境がないので、とりあえず1つのドメインだけ対応させた。現状ではとくに IPv6 の対応したからといってメリットがないのが微妙。。。

  1. トップ
  2. tech
  3. さくらのVPSのウェブサーバでIPv6の接続をうける

人と人とは利害が一致する部分と一致しない部分があり、ある言葉を受けとるときにバイアスを補正して受けとる必要がある。自分にとって無意味なことでいちいち気が乱されるようなことを避けるために、相手のポジションと自分のポジションによって生じるバイアスは補正して理解しないといけない。インターネットは大変いろんな立場の人がいるため、いちいち全部の話を真面目に聞いてるとアホになる。

利害の一致(問題なく議論が成立する)

向いてる方向性が一緒なら議論可能なので、素直に意見をもってよい。

利害が不一致(前提が共有化されていないため議論不可。交渉が必要)

このとき必要なのは議論ではなくて交渉なので、議論をもとめられても適当にやっておけば良く、話真剣に受け止める必要はない。

なお経営者対従業員的な文脈だと、経営系の人はこういうことを言うと渋い顔をするので、あくまで「経営者視点を持ってます(キリッ」ってことにして真面目に聞いているフリをしておくこと。

アナログ値

お互いの利害を直交のベクトルとして合成したときの大きさによって、どれぐらい真剣に聞くかを決めたらいい気がする。二項対立というよりは、どのぐらい話を真剣に聞くべきかというアナログ値を感じたほうがよい。

どのぐらいまで生きていられるのか気になったので試してみる。wrk を使う (ab だとうまく高負荷にできなかった) wrk は brew で入る。

ベンチの際いくつか注意する

  • Transfer/sec が十分に低いこと
    • 帯域を使いきってると負荷をかけきれない
  • Non-2xx or 3xx responses がではじめたら限界
    • 発生しなかったときは表示されない
$ wrk -c 170 -d 10s -t 16 https://...
Running 10s test @ https://...
  16 threads and 170 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   164.27ms   64.53ms 852.22ms   80.26%
    Req/Sec    61.56     22.99   170.00     72.60%
  9278 requests in 10.10s, 44.31MB read
  Non-2xx or 3xx responses: 22
Requests/sec:    918.52
Transfer/sec:      4.39MB

日記システムは、これぐらいでCPUは使いきってちょいちょいエラーが発生する感じだった。

ヤフー砲が瞬間最大で100req/sec、継続的に30req/secぐらいらしいので、現実的にはこの日記が負荷で死ぬことはまずなさそう。他のサービスも同居してて相互に影響してる(バックエンドプロセスのワーカーを共有してる)けど。

  1. トップ
  2. tech
  3. サイトの負荷のシミュレーション

サーバ移行のタイミングでつかいはじめた。監視をちゃんとするようにしようという感じ。loadavg5 と filesystem でひとまず監視。メトリクスの保存とグラフ化はフリープランだと1日だけなのであまり重視してない。

しかし、さくらのVPSはホスト不具合で落ちるってことは滅多にないので、やりたいことはサーバ自体の死活というよりウェブサーバの監視になる。このへんはプラグインでごにょごにょやってみている。パフォーマンス(応答時間のパーセンタイル値とか)とエラー数あたりでアラートが出せればいいかなという感じ。

  1. トップ
  2. tech
  3. Mackerel つかいはじめた

高誘電体 MLCC は容量が時間経過で減少していくが、約125度(キュリー温度)まで加熱すると容量が回復する特性がある、らしい。

エージングで容量が減っていくというのはいいとして、一定より温度をあげると回復するというのを知らなかったのでびっくりした。

PS3/PS4 の修理方法でとにかくホットブローするみたいなのがあるけど、はんだクラックだけではなくて容量回復するという意味合いもあるのだろうか。

ref

  1. トップ
  2. tech
  3. チップコンデンサ(MLCC)の容量回復

welq 問題でいろいろ思うところがあるけど、最終的にこれは「国会議員バカばっかり問題」と一緒ということなのだなと思って、解決にはほど遠いので気が重くなった。

「国会議員バカばっかり」って思うんだけど、あれは有権者が求めて選ばれた存在なわけで、こう思うということは有権者バカばっかりと思うに等しい。

検索エンジンの結果って、人々が求めるもののランクを上げた結果なので、その結果がバカなのって、結局人間がバカってことなんだよなあと思う。検索エンジンは人知を超えて賢くあるべきか?そして教育的であるべきか?はたしてそんな人工知能はバカな人間に受け入れられるのか (ビジネスになるのか)。

PV が稼げるのはなんでか

  • 検索エンジンにヒットしやすいということ

検索エンジンにヒットしやすいのはなんでか

  • すくなくともグーグルのアルゴリズムは「人にとって役に立つ」と判断しているということ

グーグルはなぜそういう判断をするのか

  • 人々がそれを求めるため

人々はなぜそれを求めるのか

  • 人はあんがい頭を使ってないから

ネトゲって最初からエンドコンテンツなので、スキル上げとかが途方もない。とはいえゲームなのでやってればいつか終わるぐらいのバランスにはなっている。結果僕らは学ぶことができる、時間をかければあらゆるタスクは必ず終わる。さらにネトゲに飽きて失った時間に気付いたときにもう一つ学ぶことができる。人生は有限であること。

webp のインストール

homebrew で入れる。

$ brew install libtiff
$ brew install --HEAD webp
    • HEAD つけないとコンパイル済みの TIFF 非対応バイナリがインストールされるので注意

Export Actions にシェルスクリプトを置く

~/Library/Application Support/Adobe/Lightroom/Export Actions

にスクリプトを置くと、書き出しダイアログで選べるようになる。書き出したあとスクリプトの引数に現像済みファイル名が渡されるので、これを処理するようにすればよい。

以下のようなファイルを webp.rb という名前で Export Actions フォルダに保存して実行属性をつけておく。

#!/usr/bin/env ruby

require 'logger'

logger = Logger.new('/tmp/webplog')
logger.info ARGV.inspect

begin
	ARGV.each do |f|
		dir = File.dirname(f)
		out = File.join(dir, File.basename(f, ".tif") + '.webp')
		logger.info "%s => %s" % [f, out]
		IO.popen([
			'/usr/local/bin/cwebp',
			'-metadata', 'all',
			'-preset', 'photo',
			'-q', '90',
			'-o', out,
			'--',
			f
		], :err=>[:child, :out]) do |io|
			while l = io.gets
				logger.info l.chomp
			end
		end
	end

rescue Exception => e
	logger.fatal e.inspect
end

Lightroom の書き出し設定

ファイル設定

  • TIFF
  • 8 bit/チャンネル

として

「後処理」で「webp」を選ぶ

で書きだし。

問題点

EXIF が消える。cwebp が TIFF の EXIF デコードに非対応のため

  1. トップ
  2. tech
  3. Lightroom で webp の一発書きだし

リバースプロキシとして使っている h2o で

proxy.reverse.url: http://localhost:5001/

みたいに書いていたら確率的に connection failure がでて悩んだ。結局

proxy.reverse.url: http://127.0.0.1:5001/

で解決

なぜこうなったか

/etc/hosts に ::1 localhost のエントリ(IPv6)があり、h2o は解決したアドレスのうちからランダムに1つ選択するため。プロキシ先のバックエンドサーバは IPv6 を bind しておらず、::1 が選択された場合に接続に失敗していた。

メモ

  1. トップ
  2. tech
  3. h2o の proxy.reverse.url で localhost を指定していたら確率的に connection failure

start_server が listen してないポートは以下のようなエラーになる。

cp socket:(null):80 is not being bound to the server

h2o は SIGHUP で graceful restart が可能だが、listen するポートが変わった場合は実際にlistenしている親プロセス (start_server プロセス) の再起動が必要になる。

  1. トップ
  2. tech
  3. h2o で listen するポートを増やしたときは master プロセスの再起動が必要

これまで HTTPS (443) だけ h2o で処理していたが、nginx に使ってるぶんのメモリ量がMOTTAINAIのでh2oだけでやるようにした。さようなら nginx。

複雑なことをしているホストは全くなかったので普通に書きかえていっただけ

  1. トップ
  2. tech
  3. nginx をアンインストール

↑ の写真 webp で Google Photos にアップロードしたんだけど、Chrome で見ても jpeg でしか配信されない。うーん…… いまいち webp 配信の条件がわからない。

Google Photos 上のサムネとかは webp になってるけど、大きめの画像は jpeg 固定なんだろうか

internal がデフォルトで、特に問題もないしずっとこれにしていたが、h2o が再起動されるたびにキャッシュが消えるのではないか? どうせ memcached も起動してるのでそっちにストアすべきではないか? と思いはじめたので変えてみた。

まぁ弱小ウェブサイトだとそもそも resumption かかるようなことがぜんぜんないので意味ない。Google Bot も分散アクセスのせいか全く resumption しないし。

  1. トップ
  2. tech
  3. h2o の ssl-session-resumption のストア先を memcached に

最近子どもがとなりのトトロにハマってて毎日見てるんだけど、今朝は「お父さん」と「お母さん」が病院で会話しているシーンを見て、「星野源みたいねぇ」「ガッキーみたいねぇ」とか言いだして笑った。メガネかけてると星野源みたいねぇと言うのはしばしばあるんだけど、さらに「ガッキーみたいねぇ」は初めて聞いた。

最近は直接見てないはずなので (というか親が見ようとすると嫌がる)、結構前のを覚えているようだ。

さくらのVPSのウェブサーバでIPv6の接続をうける | tech - 氾濫原 で、IPv6 アクセス環境がないと書いたが、スマフォで簡単に IPv6 接続できることを知った。

APN 設定

IIJ mio の場合、APN の設定で IPv4/IPv6を選ぶだけで優先的に v6 接続されるようになる。

全ホストの IPv6 対応

自力で確認できる環境が身近にあることがわかったので、このサーバでホストしているホストを IPv6 対応してみた (もとも IPv6 インターフェイスも listen してるので単に AAAA レコードをひけるようにしただけ)

ちゃんと接続されてるのか?

見た目が何も変わらないのが正しい状態なのだけど、IPv6 接続されてるのかよくわからないので、仕掛けをしてみた。

判定は単に REMOTE_ADDR を見て、IPv6 アドレスだったら IPv6 接続と判定する。: を含んでいたら IPv6 と判定するようにする。

このサイトの場合、ページ全体をキャッシュしているため、内容をIPv4/IPv6で出しわけると持つべきキャッシュが倍になってしまい現実的ではない。ということで、以下のようにした。

  • CSS で適当なところに content: attr(data-ip-info); と書いておく
  • JS で接続情報を取得するエンドポイントを叩き data-ip-info を埋める

ref

  1. トップ
  2. tech
  3. IPv6 対応にした

郑州炜盛电子科技 という会社の MH-Z19 という CO2 センサを買ってみました。Aliexpress で $22 ぐらいでした。

  • 非分散形赤外線吸収式
  • 電源は 3.6〜5V
  • インターフェイスは UART または PWM 3.3V
  • プリヒートは3分
  • 温度補正あり。精度は±50ppm + 5%

5V の電源と 3.3V のインターフェイスが必要ですね。

とりあえず動かしてグラフ化

Ruby 用のライブラリを書いて読みだして、GrowthForecast に投げてみました。Raspberry Pi 上で動かしています。シリアルは USB 経由で、PWM では GPIO 経由で読んでいます。

結構おもしろくて、人がいるかいないかはグラフから一目でわかるレベルです。換気するとそくざに値に反映されるのもおもしろいです。

Ruby 用のライブラリ

Ruby で読み出し用のライブラリを書いてみました。

Serial 経由の読みこみとPWM (Linux のみ。sys/class/gpio 経由) どちらとも実装してあります。

PWM

Serial は仕様書通りに書いただけですが、PWM はちょっと工夫がいるのと、データシートそのままだとちゃんと値がでず試行錯誤したのでメモしておきます。

/sys/class/gpio でのエッジトリガ

PWM はエッジトリガをしかけて時間をそこそこ正確に計測する必要があります。Linux の GPIO でやる方法がわからなかったので、ちょっと調べて以下のようにしました。

  • "/sys/class/gpio/gpio#{pin}/direction" を in に
  • "/sys/class/gpio/gpio#{pin}/edge" を both に
  • "/sys/class/gpio/gpio#{pin}/value" を開いて fd を取得
  • select でエッジを待つ
  • read で内容を読む
  • seek で戻す
  • select に戻る

select でブロックしてエッジが検出されると制御が戻ります。read したあと seek で戻さないと、次の select でブロックせずビジーループになってしまうようでした。

def self.trigger(pin, edge, timeout=nil, &block)
	self.direction(pin, :in)
	self.edge(pin, edge) #=> write "/sys/class/gpio/gpio#{pin}/edge"
	File.open("/sys/class/gpio/gpio#{pin}/value", "r") do |f|
		fds = [f]
		buf = " "
		while true
			rs, ws, es = IO.select(nil, nil, fds, timeout)
			if es
				f.sysread(1, buf)
				block.call(buf.to_i)
				f.sysseek(0)
			else
				break
			end
		end
	end
end

処理速度が不安でしたが、1ms 単位でとれれば十分なので Ruby + /sys/class/gpio でも十分でした。

PWM の場合の計算方法

データシートだと以下の計算式になっています。ここで th は PWM の high の時間、tl は low の時間、ppm は CO2 濃度です。

ppm = 2000 * (th - 2e-3) / ((th + tl) - 4e-3)

が、どうみてもデータシート通りの値がでてきませんでした。どうやら型番が一緒で、検出レンジが 0〜2000ppm のものと、0〜5000ppm のものがあるようで、2000 の部分を 5000 にするとうまく計算できました。

キャリブレーション

シリアル経由だと、ゼロ補正とスパン補正というのがあります。仕様書にどうやってキャリブレーションするか書いてないのですが、おそらく以下の手順です

  • CO2 がない環境 (窒素100%とか) でゼロキャリブレーション
  • 既知のCO2濃度の環境でスパンキャリブレーション (スパンキャリブレーション時に既知の濃度を渡します)

↑ の手順おそらく間違いなので以下の正しいと思われる手順を書きます。MH-Z19B のデータシートの手順です。

  • CO2 濃度が 400ppm の環境でゼロキャリブレーション
  • CO2 濃度は 2000ppm の環境でスパンキャリブレーション (1000ppm 以上の既知の濃度を使うこと)

まぁキャリブレーションは難しいのですが、一定期間内の最低値を外気のCO2濃度 (約400ppm) とみなしてときどき自動で補正していく方法もあるようです。

気象庁の二酸化炭素濃度のグラフを見る限り、近年は 400ppm 程度のようです。季節変動もみられますが、センサーの精度よりも小さいので無視できます。

二酸化炭素濃度の基準

ビル衛生管理上では 1000ppm を目安に換気せよと書いてあります。これは二酸化炭素の影響だけではなくて、これを目安として他の有害物質の滞留を防ぐという意味合いもあるみたいです。

また CO2 単体でも 2000ppm以上になると自覚できる症状が現れることがあるようです。

ref.

  1. トップ
  2. tech
  3. MH-Z19 という格安 CO2 センサを読んでみた

花王の漂白剤シリーズに「ハイター」というのがあるが、種類がいっぱいあってよくわからない。というところだけど、実は公式のFAQに一覧があって、どのように成分が違うのかと用途が記載されている。

大きな違いは「界面活性剤」の有無になる。汎用性が高いのは含まれていないほうなので、とりあえず買うなら「ハイターE」「月星ブリーチC」あたり、「ハイターE」は一般向けだと「ハイター 衣料用漂白剤」になる。

【業務用 衣料用塩素系漂白剤】ハイターE 5kg(花王プロフェッショナルシリーズ) - 花王

花王

5.0 / 5.0

なお基本的に「ハイター」には塩素系を期待すると思うが、酸素系の「ハイター」も存在しているので注意が必要。

消毒用途では基本的に10倍希薄して使うことになっている (商品は6%だが、塩素は揮発してしまうため 5% 扱いとする)。0.5%以上 (5000ppm) の濃度で使用しても消毒効果は上がらず、残ってしまう時間が長くなるだけなので、必ず守る。

なお次亜塩素酸ナトリウムが重宝される理由としては、蛋白質と接触すると食塩に変化するため残留リスクが低いことにある。

ref

  1. トップ
  2. tech
  3. ハイターの種類と選びかた。最も汎用性が高いのは

QAサイトで間違ったことが書いてあると、正解を書きたくなる心理を利用して、どんな回答にもまず間違った答えを機械が書くというのはどうか (あっててもいいけど)

MH-Z19 という格安 CO2 センサを読んでみた | tech - 氾濫原 の続きです。

Raspberry Pi で喋らせる

Open jtalk で喋るようにしてあるので

  • 1000ppm を超えたら換気をうながす (アラート)
  • 600ppm 未満になったら告知する (アラート解除)

みたいにした。

alert = false

loop do
        begin
                ppm = get_latest.last
                case
                when ppm > 2000
                        system("jsay.sh", "CO2濃度が2000ppmを超えました。ただちに換気を行ってください")
                        alert = true
                when ppm > 1000
                        system("jsay.sh", "CO2濃度が1000ppmを超えました。換気を行ってください")
                        alert = true
                when alert && ppm < 600
                        alert = false
                        system("jsay.sh", "CO2濃度が600ppmまで下がりました")
                end
        rescue Exception => e
                p e
        end
        sleep 5 * 60
end

get_latest は GrowthForecast からデータをとってくるやつ。センサーからデータを読むプロセスは別にしてあるので、間接的にgfからとってきてる。

常に表示する

家で余っているスマフォに常時表示するようにした。これも GrowthForecast からデータをとる。具体的には

グラフは vis.js を使ってみた。結構つかいやすい。example のうちの 404 Not Found を参考にしたらうまくできた。

今後

GrowthForecast にデータを投げてるので、gf の API をメインで使ってみたが、gf はそういう用途のためのものじゃないので、かなり無駄感がある。WebSockeet 経由でデータを転送するゲートウェイみたいなのをまず作ったほうがいいかもしれない。

  1. トップ
  2. tech
  3. CO2 センサの可視化

ミラクルニキ-お着替えコーデRPG - Apps on Google Play これ。公式サイトはここ

元々中国のアプリ「奇迹暖暖」で、それをローカライズして販売してるっぽい。暖暖シリーズはいくつかあるっぽく、過去にも日本語版ローカライズ版はでてたりするっぽいがやったことはない。ありがちな日本語に不自然なところはあまりない(が、UIではときどきある)。日本の提供会社の情報があんまりなくて怪しい以外は気になるところはない。

とりあえず最高に楽しい。いろいろゲームやってみた結果、着せ替えができさえすれば楽しいってことには薄々気付きつつあったけど (ポケモン・サン/ムーンやったら着せ替えの衣装が足りなくてものたりなかった)、このゲームは完全に着せ替えオンリーを極めててよい。着せ替え対象のキャラクターは一人だけ(ニキ)だけど、衣装数が極めて多く、髪型も変えれるので気にならない。

キャラクターのデザイン自体も「可愛いが男媚びも女媚びもしてない」ぐらいのバランスだし、衣装も細かく描かれててめっちゃいい。ニキのキャラ設定自体も鼻につかない感じで余計な部分はない。あえていうなら付属してくる変なゆるキャラみたいのがウザいぐらい。

基本的にあんまりハマりどころはないけど、ちょいちょいキツいステージがある。4-12 は衣装がほぼ固定で高得点が出しにくく S がなかなかとれなかった。S限定の衣装をガール級の 5-12 で使うので手に入れたかった (なお 5-12 は 3-1(プリンセス) や 4-5(プリンセス) のチャイナでも代用可能)

結局のところ、衣装でどうにかならないのはスキルでどうにかするしかないことがわかった。このステージだと脱がされないので、厳しい視線を回避してバフかければSとれそう。

自分のときはこれでいけた

h2o の status ディレクティブのJSON出力を眺めていたら、duration-25 とか connect-time-50 とか 不思議なプロパティがいくつか含まれることに気がついた。しかしどうも全て0のようであるので、なんらかのコンパイルオプションなのだろうか?と思ってちょっと調べた。

結局これは global で duration-stats: ON すると有効になるみたいだった。そうすると以下のように数字が埋まりはじめる。

"duration-0": 0,
"duration-25": 0,
"duration-50": 0,
"duration-75": 4731,
"duration-99": 26829

おそらくμs単位のパーセンタイル値で、この場合たとえば duration-75 は 75% のリクエストが 4731μs 以内に納まるみたいなやつだと思う。

今のところドキュメントには書いてないっぽい、というかリリースされてないっぽいので HEAD が必要?なのかな。

メモ

  • h2o_now は ms 単位
  • durations は h2o.h の COMPUTE_DURATION で *delta_usec = h2o_timeval_subtract*1; となっていてμsになってるっぽい
  • server-status はどこのホストに書いても同じのがかえってくる (あくまでサーバ共通のステータスがとれる)
  1. トップ
  2. tech
  3. h2o の duration stats

mackerel にメトリクスとして送る

mackerel-plugin-h2o みたいなのは今のところないっぽい? ので、status/json からとれる内容をポストするプラグインを書いた。

https://github.com/cho45/mackerel-agent-plugins/tree/mackerel-plugin-h2o/mackerel-plugin-h2o

mackerel プラグインの書きかた

他のプラグインにならって go-mackerel-plugin-helper を使ってみた。FetchMetrics の返り値でちょっと悩んだけど、以下のようにすればよさそう。

  • FetchMetrics ではとにかくとれる値をすべて map に詰めてかえす
  • GraphDefinition でグラフと値の関連付けを行う

GraphDefinition に含まれないメトリクスは、FetchMetrics で map に詰めても送信されない。これが最初理解できてなかったので、FetchMetrics でややこしいことをやってしまったりした。

  1. トップ
  2. tech
  3. h2o の status/json を mackerel に送る

いままでSANYO時代のエネループ充電器を使っていたが、必要性が増してきたのでこのようなものを買った。

ニッケル水素充電池用充電器 単3単4兼用 ホワイト TGX12 - TGX

TGX

5.0 / 5.0

単3と単4どちらも充電できる。一本ずつ充電管理されているのでバランスとかは考えなくて良い。

端子がちょっと固くて、しっかり押しこんでやらないと入らない。一応充電状態にはなってしまうので気をつける。写真のうちいくつかはちゃんと入ってない (撮ったときは気付かなかった) 9 と 10 はちゃんと入っている。1とか4はちゃんと入ってない。

音がちょっとうるさい

本体側で若干コイル鳴きしててうるさい。といってコイル鳴きなので、かなり静かな環境でなければ聞こえない。

待機電力

一本も電池を入れてない場合、液晶のバックライトが消えてスタンバイ状態になるようだ。このときの消費電力は 0.3Wぐらい。

全て充電完了になっている場合、液晶のバックライトがついて充電完了を知らせる表示になる。このときの消費電力は0.5Wぐらい。

その他

ACアダプタは12V 1.5A (18W) のものが付属していた。ポートあたり最大 500mA (1.2V) らしいので十分っぽい。

  1. トップ
  2. tech
  3. 12本のNiMHを充電できる充電器を買った

意識高い (意識高い系ではなく、真の) みると、ただただつらい。アドベントカレンダーで毎日意識高いエントリが目につく。精神が攻撃されている。もっと気楽に生きていきたい………

先にまとめておくと、おむつの支出が支配的で、それ以外だと直接子ども用のものをあんまり買ってない。絵本とかをあんまり買ってない気がする。

おしりふき

【おしりふき 詰替用】グーン 肌にやさしいおしりふき タップリッチ 840枚(70枚×12個) [ケース販売] 【Amazon.co.jp限定】 - タップリッチ

タップリッチ

5.0 / 5.0

3ヶ月に1回ぐらい買ってるっぽい。

おむつ

メリーズ L サイズ → グーン Big サイズ → グーン Big より大きいサイズ と変わった。「Big サイズ」と「Big より大きいサイズ」はまだ併用してて、保育園で昼寝のときに漏れる問題が発生して保育園用のだけワンサイズ大きくしている。

ざっと合計したら60454円だった。だいたい5000円/月ぐらい。だけど保育料の支配率が高いので誤差みたいなもの……

子ども向けの本

おもちゃ

クリスマスプレゼント多くない? って感じだけど、祖父母分もはいっている (祖父母からは現金が渡ってきて、こちらで適当にあわせて買うという方式)

家具

育児関係で大人用のやつ

Raspberry Pi で Wi-Fi が一度切れると二度と繋がらない、という現象に遭遇した。しかたないので変なことをした。

#!/bin/sh

msg() {
        echo $1
        logger $1
}

while true
do
        if ifconfig wlan0 | grep -q "inet addr:"; then
                sleep 60
        else
                msg "Network connection down! Attempting reconnection."
                ifdown --force wlan0
                sleep 5
                ifup wlan0
                /etc/init.d/dhcpcd reload
                sleep 30
        fi
done

ifplugd とかも触ってみたけど、これが結局確実っぽい。なんかどうも dhcpd が ifup 時に反応してくれなくて、手動で reload してる。うーん。もっと一発で解決する方法があればいいんだけど……

ref

  1. トップ
  2. tech
  3. Raspberry Pi の Wi-Fi 自動再接続

systemd になって inittab が消滅したので途方にくれましたが以下のようにするとできました。

serial ログイン

UART ピンからのログインの場合

sudo vim /lib/systemd/system/serial-getty@.service
ExecStart=-/sbin/agetty --keep-baud 115200,38400,9600 %I $TERM

となっているので

ExecStart=-/sbin/agetty -a pi --keep-baud 115200,38400,9600 %I $TERM

とする。

なお、この状態でシェルで reset を実行すると halt や reboot がハングするようになる。謎…… reset を殺して対応してるけど理由がわからなくて気持ちわるい。systemd がおかしいのだろうか?

console ログイン

ssh 経由とかの場合。

sudo raspi-config から設定可能です。なので基本的には raspi-config でやったほうがよさそう。

以下は手動でやる方法

sudo vim /lib/systemd/system/getty@.service 
ExecStart=-/sbin/agetty --noclear %I $TERM

となっているので、以下のように

ExecStart=-/sbin/getty --noclear -a pi %I $TERM
  1. トップ
  2. tech
  3. Raspberry Pi 3 で自動ログイン

MH-Z19 という格安 CO2 センサを読んでみた | tech - 氾濫原 を書いたあと、追加で3つ買って、4つ手元にある状態ですべて繋げて値を読んでみると、でてくる値がとんでもないのが2つあった。窓全開で十分に換気されている状態でも 800〜900ppm になってしまう。

センサの種類 (2000ppm or 5000ppm) が違うのか? と思ったけど、UART の値も PWM の計算値も同じ (UART では ppm が直接出力されるので、計算値の答えあわせになる) なので関係なさそう。

その後よくよく比較してみると、どれも全く動きかたがちがう。傾き(感度)も違う。やっぱこれはキャリブレーションをなんとかしてやるしかない気がする。うーん。

ゼロ点とスパン点どっちも難しい。なんか簡単にできる方法がないか考え中

  1. トップ
  2. tech
  3. MH-Z19 の個体差とキャリブレーションの必要性

OWON デジタルオシロスコープSDS7102 超薄型 1Gsサンプリング100MHzFFT機能付カラーポータブルフルセット【国内正規品】 - OWON

OWON

3.0 / 5.0

OWON SDS7102 大変つかってるが安いだけあって品質が微妙な点がいくつかある。特にロータリーエンコーダーがひどくて、ちゃんとレンジ切り替えができないことが多くあってイライラする。同様の現象が起きてないか検索したところいくつかヒットしたのでやってみた。

おおむね 500 Can't connect to www.candrian.gr:443 (certificate verify failed) を参考にすればいいのだが、ちゃんと外れないところがあった。

分解

裏蓋

  • 周囲のビス4本
  • アースのビス
  • 下側の接続ポートのビス

をはずす。裏蓋はハメこみなので、カードとかで隙間をひろげながらはずす。

電源スイッチがひっかかるので、これは押しながら抜く。

開きつつあると、裏蓋に繋がる電源線が3つあるので、基板側のコネクタとビスをはずす。

ロータリーエンコーダの基板まで

  • 写真のうち7つのビス (赤丸)
  • コネクタ1つ (紫丸)

をはずす。これでモニタも含めてすっぽり外れる。フラットケーブルとかで繋がってるので慎重にやる。

キャパシタの追加

赤丸部分がロータリーエンコーダなので、ここに 1608 サイズの 0.1μF を2つずつはんだづけする。

戻す

はずしたコネクタを通すところを間違えるとハメられないので気をつけてハメていく。それ以外はとくに問題ないはず。

効果

全体的に変な動きは減った。しかし中央(2ch) のレンジをかえるロータリーエンコーダはそもそも不良っぽくて完全には治らなかった。

ref.

  1. トップ
  2. tech
  3. OWON SDS7102 のロータリエンコーダの不具合をなおす

Aliexpress で歪みゲージ (ロードセル 1kg) と、HX711 のモジュールを買ったので試してみました。

歪みゲージのつかいかた

歪ませる必要があるので、このように上下に板をつけて挟みこみます。このとき、多少すきまが必要なのでワッシャーなどをかまします。

今回使った歪みゲージのスペックは以下の通りです。(コピペ) また、M4 と M5 のネジが切ってありました。

Rated Load: 1Kg
Rated Output: 1.0mV/V±0.15mV/V
Zero Output: ±0.1mV/V
Creep: 0.03%F.S./30min
Input End: Red+ (power), Black-(power)
Output End: Green+(signal), White-(signal)
Recommended operating voltage: 3 ~ 12 VDC
Maximum operating voltage: 15 VDC
Input Impedance: 1115±10%Ω
Output Impedance: 1000±10%Ω
Protection class: IP65
Total Size: approx. 3.15 x0.50 x 0.50 inch
Cable: 0.8 x 20 cm (diameter x length)
Material: Aluminum Alloy
Weight: 30g

HX711 で読んでみる (Arduino)

手に入れたのは↑のようなモジュールです。適当に配線して読みます。

以下のライブラリを使ってみました。

README の通りにスケールを設定する必要があります。

#include <Arduino.h>
// https://github.com/bogde/HX711
#include "HX711.h"

const int DT_PIN = 2;
const int SCK_PIN = 3;

HX711 scale;

void setup() {
	Serial.begin(9600);
	Serial.println("start");
	scale.begin(DT_PIN, SCK_PIN);

	Serial.print("read:");
	Serial.println(scale.read());

	scale.set_scale();
	scale.tare();

	Serial.print("calibrating...");
	delay(5000);
	Serial.println(scale.get_units(10));

	scale.set_scale(-1536.00);
	scale.tare();

	Serial.print("read (calibrated):");
	Serial.println(scale.get_units(10));
}


void loop() {
	Serial.println(scale.get_units(10), 1);

	scale.power_down();
	delay(500);
	scale.power_up();
}

このようなコードを書いて、

  1. 何ものせずに起動
  2. calibrating... が表示されたらすかさず1円玉を1枚載せる (1g)
  3. 値を読む
  4. scale.set_scale(...); に値を埋めこむ

という方法をとりました。

どんな感じか

こんな感じで値が読めました。1円玉を1つずつ増やして5枚まで載せてみました。

ref.

  1. トップ
  2. tech
  3. 歪みゲージ(ロードセル)と HX711 を使って重量計測する (Arduino)

同級生との飲み会で若干ハメをはずして飲みすぎて久しぶりに激しい二日酔いになってしまった。二日酔いになるたびに二日酔いをすみやかに解消する方法を調べてるけど、結局ないってことはわかっている。

必要なのは時間と水分(分解に必要)なので、スポーツドリンクをこまめに飲みつつ、梅干し純を舐めて、身体をあたためつつ布団で寝ていた。

アサヒグループ食品 梅ぼし純 24粒×10個 - アサヒグループ食品

アサヒグループ食品

5.0 / 5.0

梅干し純は気持ち悪いのが軽減されるうえに塩分が得られる。スポーツドリンクにも塩は入っているけど、少なめになっているらしいので、ちょっと追加するのがちょうどいいのではないかという狙い。

MH-Z19 の個体差とキャリブレーションの必要性 | tech - 氾濫原 というのを書きましたが、とりあえず 400ppm 環境は比較的簡単に作れるのだから、それでキャリブレーションしてみることにしました。

その過程でこのセンサの「ゼロキャリブレーション」について誤解していたことがわかりました。 MH-Z19 という格安 CO2 センサを読んでみた | tech - 氾濫原 に追記してありますが、ここでいう「ゼロ」は 400ppm 環境のことであるらしく、0ppm の環境は必要ありませんでした。また「スパンキャリブレーション」は最低でも1000ppm以上、2000ppm以上推奨とのことでした。MH-Z19 のドキュメントではなく MH-Z19B のドキュメントのほうが詳しく書いてあり、こちらを参考にしました。

スパンキャリブレーションはそのような特定環境をつくるのが難しいので、今回は「ゼロキャリブレーション」だけを行ってグラフにしばらく投稿してみました。

ゼロキャリブレーションの方法

窓をあけて換気扇をつけ、周辺環境をできるだけ400ppmに近付けます。最低でも30分ぐらいは換気します。室内であっても十分に換気ができていれば400ppm前後にはできるので、気をつけるのは息を吹きかけないようにする (自分が風上にいないこと) ぐらいです。人間の呼気中の CO2 は 4% (40000ppm) ぐらいなので、普通に影響します。

センサはシリアル経由で繋ぎました。起動に数分時間がかかるので放っておきます。

以下のスクリプトを実行します。

https://github.com/cho45/ruby-mh-z19/blob/master/examples/cal.rb

短いのでコピペすると

#!/usr/bin/env ruby

$LOAD_PATH << "#{File.dirname(__FILE__)}/../lib"

require 'mh-z19'

co2 = MH_Z19::Serial.new(ENV['PORT'])
# wait sensor startup
loop do
	detail =  co2.read_concentration_detail
	p detail
	case detail[:status]
	when 0
		p "booting"
	when 1
		p "startup"
	when 64
		break
	end
	sleep 1
end

co2.calibrate_zero_point

sleep 3

p co2.read_concentration_detail

こんな感じになります。この MH-Z19 はドキュメントに書いてないのですが、ステータスビットや認識している温度も返すようで、read_concentration_detail だとそれらもとれるような実装にしてあります。

グラフ

例によって1日ぐらい放置してみました。スパンキャリブレーションしていないので、やはり最大値に差があります。とはいえ 100〜200ppm 以内には入ってる感じなので、実用的にはとりあえず十分かもしれません。いずれにせよ何もしない場合よりは遥かにマシなので、必ずやったほうがよさそうです。

  1. トップ
  2. tech
  3. MH-Z19 のゼロキャリブレーションをしてみる

寝室に置いてみたいので ESP8266 (ESP-WROOM-02) で動かして GrowthForecast にポストするようにしてみた。

MH-Z19 を PWM 経由で読んでいる loop 関数だけ抜きだすと以下のような感じ。とりあえず割込みは使ってない。

void loop() {
	ArduinoOTA.handle();

	static uint32_t prevTime = 0;
	static uint8_t lastState = 0;
	static uint32_t	th;
	static uint32_t	tl;
	int state = digitalRead(PWM_INPUT);
	if (lastState == state) {
		// nothing to do
	} else {
		lastState = state;
		uint32_t now = millis();
		if (!prevTime) {
			prevTime = now;
			return;
		}
		uint32_t interval = 0;
		if (prevTime <= now) {
			interval = now - prevTime;
		} else {
			interval = 0xffffffff - prevTime + now + 1;
		}
		prevTime = now;
		if (state == 1) {
			tl = interval;
			if (tl && th) {
				uint32_t cycle = tl + th;
				if ((uint16_t)(1004 * 0.95) < cycle && cycle < (uint16_t)(1004 + 1.05)) {
					uint16_t ppm = 5000.0 * ((float)(th - 2) / (float)(cycle - 4));
					Serial.printf("%d ppm (cycle %d / th: %d, tl: %d)\n", ppm, cycle, th, tl);
					gf.post("/home/sensor/co2_1", ppm);

					// reset count
					th = 0; tl = 0;
					prevTime = 0;
				} else {
					// error
					Serial.println("error");
				}
			}
		} else
		if (state == 0) {
			th = interval;
		}
	}
}
  1. トップ
  2. tech
  3. ESP8266 Arduino で CO2 センサー MH-Z19 を読む

RabbitMQ + MQTT で Pub/Sub サーバを立てることにしました。

いろいろなセンサーのグラフ化にあたって GrowthForecast へ直接 POST を行っていましたが、やはり一旦メッセージブローカー的なものをいれたほうがよさそうだという感じになってきました。

なぜメッセージブローカーが必要か

センサーデータを複数のプログラムから使いたい場合、特にほぼリアルタイムで情報を得たいようなケースだと、直接各アプリケーションに投げるのではなくて、センサーはある一箇所に値を投げることだけを考え、アプリケーションはある一箇所からデータを受けとることだけを考えるように分離したくなります。

例えば今まではセンサーデータをアプリケーションであるグラフサービスに直接投げていましたが、これだとセンサーデータをさらに別のデバイスから読みだして表示するといった場合に、本来の用途ではないグラフサービス側のAPIに問合せたりする必要があって不便です。

MQTT を選択

MQTT はキューがない (最後の値だけ保存する/Retain) Pub/Sub のメッセージ配信プロトコルで、組込み系だとそこそこメジャーなようです。ググってみるとクライアント実装はそこそこ充実しています。

サーバ実装がいまいちコレというのがない気がするのですが、RabbitMQ のプラグインに MQTT プロトコルサポートがあるので、これを利用するのが比較的よさそうでした。

これ系のプロトコルは MQTT 以外にもいろいろあって、RabbitMQ の本来の用途である AMQP も競合プロトコルになります。AMQP より MQTT が好まれるのはプロトコルのシンプルさのためですが、機能的には AMQP が勝ります。

RabbitMQ のインストール

ひとまず Ubuntu のパッケージをそのままつかうことにしました。

sudo apt-get install rabbitmq-server

Rabbit MQ は Erlang で書かれているので、Erlang 関係のパッケージが大量にはいります。

インストール直後から起動していて、rabbitmqctl status でステータスが見れます (root 権限が必要です)。デフォルトではクラスタリング用のポート25672と、AMQP 用のポート 5672 が listen されていました。

sudo rabbitmqctl status

続いて mqtt プラグインを有効にしておきます。

sudo rabbitmq-plugins enable rabbitmq_mqtt

このコマンドは自動的に設定が反映され、status を見ると mqtt を 1883 で listen していることがわかります。

RabbitMQ の設定

適当に見てみると /etc/rabbitmq/rabbitmq-env.conf というのが最初からありますが、これは環境変数設定ファイルなのでとりあえずそのままにしておきます。

ログとして /var/log/rabbitmq/$node@$host.log というファイルがあり、これの冒頭に

node           : rabbit@stfuawsc
home dir       : /var/lib/rabbitmq
config file(s) : /etc/rabbitmq/rabbitmq.config (not found)
cookie hash    : 
log            : /var/log/rabbitmq/rabbit@stfuawsc.log
sasl log       : /var/log/rabbitmq/rabbit@stfuawsc-sasl.log
database dir   : /var/lib/rabbitmq/mnesia/rabbit@stfuawsc

というログが出ています。この通り設定ファイルは /etc/rabbitmq/rabbitmq.config になりますが、まだないので作る必要があります。

サンプル設定ファイルがあるのでとりあえずこれをコピペしてつくるのがよさそうです。

# とりあえず眺める 
zless /usr/share/doc/rabbitmq-server/rabbitmq.config.example.gz

# コピペからはじめる
sudo sh -c 'zcat /usr/share/doc/rabbitmq-server/rabbitmq.config.example.gz > /etc/rabbitmq/rabbitmq.config'

Erlang の設定ファイル形式なのでちょっと読みにくいです。

  • % から行末まではコメント
  • シングルクオートはアトム (Ruby でいうところのシンボルと同様)
  • ダブルクオートはその文字列の数値のリスト
  • 括弧付きダブルクオートはバイナリ (Bit Syntax)
  • 余計なカンマがあると怒られる (ケツカンマ問題がある)

設定方針

ここでは方針として以下のようにします。

  • MQTT を使う
  • TLS を使う

TLS の設定では取得済みの Let's Encrypt の証明書をそのまま流用します。ただ、Let's Encrypt がつくる /etc/letsencrypt/live 以下が root 以外読めないようになっている一方、RabbitMQ は証明書ファイルを rabbitmq ユーザで読もうとするようで eacces がでます。

しかたないので以下のようにしてアクセスを許可するように変えてしまいました。なにかもっとスマートに解決したほうがいいと思うのですが、思いつきませんでした。

sudo chmod 0755 /etc/letsencrypt/{live,archive}

設定

以下のようにしてみました。

%% -*- mode: erlang -*-
%% ----------------------------------------------------------------------------
%% RabbitMQ Sample Configuration File.
%%
%% See http://www.rabbitmq.com/configure.html for details.
%% ----------------------------------------------------------------------------
[
 {rabbit,
  [
   {ssl_listeners, [5671]},
   {handshake_timeout, 10000},
   {log_levels, [{connection, info}, {channel, info}]},

   {ssl_options, [{cacertfile,           "/etc/letsencrypt/live/cho45.stfuawsc.com/fullchain.pem"},
                  {certfile,             "/etc/letsencrypt/live/cho45.stfuawsc.com/cert.pem"},
                  {keyfile,              "/etc/letsencrypt/live/cho45.stfuawsc.com/privkey.pem"},
                  {verify,               verify_peer},
                  {fail_if_no_peer_cert, false}]},

   {auth_mechanisms, ['PLAIN', 'AMQPLAIN']},
   {auth_backends, [rabbit_auth_backend_internal]},

   {ssl_handshake_timeout, 5000}
  ]},

 {rabbitmq_mqtt,
  [
   {allow_anonymous, false},
   {vhost, <<"/">>},
   {exchange, <<"amq.topic">>},
   {subscription_ttl, 1800000},
   {prefetch, 10},

   {tcp_listeners, [1883]},
   {ssl_listeners, [8883]}
  ]}
].

ユーザの追加

もともとある guest/guest は localhost からだけ接続が許されているのでそのままにして、外部から使う用のユーザを追加します。

# guest しかない
$ sudo rabbitmqctl list_users
Listing users ...
guest   [administrator]

# ユーザ追加
$ sudo rabbitmqctl add_user tsun pass
Creating user "tsun" ...

# 確認
$ sudo rabbitmqctl list_users        
Listing users ...
guest   [administrator]
tsun    []

# パーミッションを追加 conf write read の順で指定する。これは正規表現。ここでは全権
$  sudo rabbitmqctl set_permissions tsun  ".*" ".*" ".*"
Setting permissions for user "tsun" in vhost "/" ...

繋いでみる

クライアントに rubygems の mqtt を使ってみます

gem install mqtt
#!/usr/bin/env ruby

require 'mqtt'
require 'thread'

# MQTT::Client seems not thread safe
mutex = Mutex.new

pub_thread = Thread.start do
	mutex.lock
	MQTT::Client.connect(
		host: '127.0.0.1',
		port: 8883,
		ssl: true,
		username: 'guest',
		password: 'guest',
	) do |client|
		mutex.unlock
		p [:pub, client]


		10.times do
			sleep 1
			p :publish
			client.publish("test", "this is test", false)
		end
	end
end

sub_thread = Thread.start do
	mutex.lock
	MQTT::Client.connect(
		host: '127.0.0.1',
		port: 8883,
		ssl: true,
		username: 'tsun',
		password: 'dere',
	) do |client|
		mutex.unlock
		p [:sub, client]
		client.subscribe("test")
		client.get do |topic,message|
			p [ topic, message ]
		end
	end
end

pub_thread.join

こんな感じのコードで動いていることを確認できるはずです。ただし guest を指定しているので、同一ホストで動かす必要があります。

ポートを開ける

基本の動作確認ができたので、パブリックにアクセス可能にするためポートをあけます。ufw で 8883 (MQTT with TLS) だけをあけました。他はいまのところ使用予定がないので閉じたままです。listen 自体しないほうがより安全ですが、サーバ内でごにょごにょすることはありそうなのでそのままにしています。

 sudo ufw allow 8883

外部から、さきほど作ったユーザで接続確認を行ってとりあえずセットアップ完了です。

ref

  1. トップ
  2. tech
  3. センサーデータ用に RabbitMQ + MQTT をセットアップする

ウェブページから JavaScript で MQTT ブローカーに送られてくるデータを取得したかったので、MQTT over WebSocket を試してみました。

RabbitMQ 3.6.6 をいれる

Ubuntu のレポジトリのは 3.5.7 と古いので、Installing on Debian / Ubuntu にしたがって 3.6.6 にします。

既に古いのが入っていても、説明通りに RabbitMQの apt レポジトリを設定して、 apt-get update して install したら自動でアップグレードされます。

プラグインをいれる

rabbitmq_web_mqtt をいれます。これは Community Plugins にありますが、 RabbitMQ のチームが作っているので安心感があります。プラグイン自体が比較的新しくて、RabbitMQ 3.6.1 以降でないと使えません。

$ wget --content-disposition https://bintray.com/rabbitmq/community-plugins/download_file\?file_path\=rabbitmq_web_mqtt-3.6.x-14dae543.ez
$ sudo mv rabbitmq_web_mqtt-3.6.x-14dae543.ez /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.6/plugins/
$  sudo rabbitmq-plugins list | grep web_mqtt
[  ] rabbitmq_web_mqtt 

$ sudo rabbitmq-plugins enable rabbitmq_web_mqtt
The following plugins have been enabled:
  cowlib
  cowboy
  rabbitmq_web_mqtt

Applying plugin configuration to rabbit@stfuawsc... started 3 plugins.

$ sudo rabbitmq-plugins list | grep web_mqtt    
[E*] rabbitmq_web_mqtt    

設定する

デフォルトで 15675 が listen されています。このままでいいのですが、念のため設定を書いておくことにしました。

設定例がないのでソースを読むしかないようです。rabbitmq_web_mqtt.schema が設定ファイルのスキーマのようなので、これを参考に以下のようにしました。

 {rabbitmq_web_mqtt,
  [
   {tcp_config,[{port, 15675}]}
  ]
 }

WebSocket 経路の TLS 化はリバースプロキシ (h2o) で行うので、このサーバ自体には TLS の設定を書いていません。

リバースプロキシ

HTTP フロントエンドの h2o からポート 15675 にリバースプロキシするように設定しました。これで外部から接続可能になります。

なおh2oは定期的にWebSocket接続を切るようになっているので、クライアント側で再接続するコードが必須です。

WebSocket から繋ぐ

認証はどうなるのか? という疑問があるところですが、MQTT レイヤーでのユーザ認証があります。Upgrade 時に Origin による制限などはかけられないようです。

rabbitmq-web-mqtt-examples というレポジトリがあり、これを参考にすれば簡単に接続できます。

ライブラリとして Eclipse Paho の JavaScript 版を使っています。WebSocket のエンドポイントを指定する以外は普通にMQTTするのと変わりません。

var TOPIC = "/foo/bar/baz";
var USER = "foo";
var PASS = "bar";
function mqttConnect() {
	var client = new Paho.MQTT.Client(
		location.hostname,
		location.port || 80,
		"/mqtt", "myclientid_" + Math.random().toString(32)
	);

	client.onConnectionLost = function (responseObject) {
		console.log(responseObject);
		// reconnect
		setTimeout(mqttConnect, 1000);
	};

	client.onMessageArrived = function (message) {
		var data = Number(message.payloadString);
		console.log(data);
	};

	client.connect({
		userName: USER,
		password: PASS,
		timeout: 3,
		onSuccess: function () {
			console.log('onSuccess');
			client.subscribe(TOPIC, {qos: 1});
		},
		onFailure: function (message) {
			console.log('onFailure', message.errorMessage, message);
		}
	});
}

ブラウザ上の WebSocket の場合、TLS はブラウザ側でやってくれるので、普通の MQTT with TLS の接続で考えることよりも少なくて楽です。

ref

  1. トップ
  2. tech
  3. RabbitMQ で MQTT over WebSocket

表題の通りでハマってしまった。

各ソフトウェアのバージョン

$  sudo rabbitmqctl status
...
{rabbit,"RabbitMQ","3.6.6"}
...
$ cat /usr/lib/erlang/releases/18/OTP_VERSION 
18.3

rabbitmq.config

{versions, ['tlsv1.2', 'tlsv1.1', tlsv1]}

接続確認

openssl s_client で接続確認してみる。

tls1.2

$ openssl s_client -connect 127.0.0.1:5671 -tls1_2 < /dev/null


これ問題なかった。

tls1 tls1.1

どっちもなぜか同じように以下のようなエラーになる。

$ openssl s_client -connect 127.0.0.1:5671 -tls1_1 < /dev/null 
CONNECTED(00000003)
139728874526360:error:1409442F:SSL routines:ssl3_read_bytes:tlsv1 alert insufficient security:s3_pkt.c:1472:SSL alert number 71
139728874526360:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:656:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 0 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.1
    Cipher    : 0000
    Session-ID: 
    Session-ID-ctx: 
    Master-Key: 
    Key-Arg   : None
    PSK identity: None
    PSK identity hint: None
    SRP username: None
    Start Time: 1483074781
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---

解決方法

結局これは Erlang の ssl ライブラリの問題らしく、基本的には Erlang のバージョンをあげる必要があるっぽい。しかしRabbitMQ 3.6.1 / Erlang 18.3 TLS insufficient security failures からリンクがある通り以下のようにするとうまくいった。

                  {versions, ['tlsv1.2', 'tlsv1.1', tlsv1]},
                  {ciphers, ["ECDHE-ECDSA-AES256-GCM-SHA384","ECDHE-RSA-AES256-GCM-SHA384",
                             "ECDHE-ECDSA-AES256-SHA384","ECDHE-RSA-AES256-SHA384", "ECDHE-ECDSA-DES-CBC3-SHA",
                             "ECDH-ECDSA-AES256-GCM-SHA384","ECDH-RSA-AES256-GCM-SHA384","ECDH-ECDSA-AES256-SHA384",
                             "ECDH-RSA-AES256-SHA384","DHE-DSS-AES256-GCM-SHA384","DHE-DSS-AES256-SHA256",
                             "AES256-GCM-SHA384","AES256-SHA256","ECDHE-ECDSA-AES128-GCM-SHA256",
                             "ECDHE-RSA-AES128-GCM-SHA256","ECDHE-ECDSA-AES128-SHA256","ECDHE-RSA-AES128-SHA256",
                             "ECDH-ECDSA-AES128-GCM-SHA256","ECDH-RSA-AES128-GCM-SHA256","ECDH-ECDSA-AES128-SHA256",
                             "ECDH-RSA-AES128-SHA256","DHE-DSS-AES128-GCM-SHA256","DHE-DSS-AES128-SHA256",
                             "AES128-GCM-SHA256","AES128-SHA256","ECDHE-ECDSA-AES256-SHA",
                             "ECDHE-RSA-AES256-SHA","DHE-DSS-AES256-SHA","ECDH-ECDSA-AES256-SHA",
                             "ECDH-RSA-AES256-SHA","AES256-SHA","ECDHE-ECDSA-AES128-SHA",
                             "ECDHE-RSA-AES128-SHA","DHE-DSS-AES128-SHA","ECDH-ECDSA-AES128-SHA",
                             "ECDH-RSA-AES128-SHA","AES128-SHA"]},
                  {honor_cipher_order, true},
https://gist.github.com/ae6rt/2fdcc46119821cf490c8f3c444bd11d4#file-rabbitmq-config-L58

ref

  1. トップ
  2. tech
  3. RabbitMQ に TLSv1.1 で接続しようとしても tlsv1 alert insufficient security と言われる

日記を読みかえしてみたら今年もいろいろやっていた。年末にせめて自分で自分を承認してあげたい。おおまかには

  1. 高周波関係
  2. アンテナアナライザ製作
  3. キーボード製作
  4. 3D モデリング

の順に興味がうつりかわり、同時にウェブ技術とかNC切削とか、普段通りという感じのこともやっていた。振り替えってみると思ったより密度高く生きてるが、主観的にはそうでもないというかやる気がなさすぎて困っているという実感がある。なぜだろう……

ウェブとか HTTP/2 関係

h2 対応とか、このウェブサイトの最適化を結構やっていた。ハードウェア触る気力がなくなると、ノートPCだけでぼーっと最適化するみたいな流れ…… ウェブ技術はなんというか、仕事じゃなければあんまり気を張らなくても実装できるので、とりあえずやっておくというか、良い言いかたをすれば癒し的側面がある。

サーバサイドで Mathjax するあたりとかは結構がんばったし効果が高かった。

h2 対応のために h2o を導入したので、それ関係のエントリがかなり多い。

類似エントリみたいな、ちょっとアルゴリズムよりのこともやってみた。ただ実装するというより SQLite にこだわって工夫してやってみたところは良かった。仕事で SQLite 使うことはないので役には立たない。アルゴリズムの実装がものすごく苦手なんだけど (こういうこと言うとバカにされまくる……)、ちょっと頑張った例。TF-IDF なんてクソみたいに簡単なアルゴリズムではあるが……

仕事だと分業のおかげで、こういう上から下まで一貫してチューニングするみたいなのはやる機会がない。

おうちハック的なのもやっている。

アンテナアナライザなど高周波関係

デジタルSWR計。アンテナアナライザへの布石。

アンテナアナライザ関係。いろいろ勉強になっておもしろかった。 今年前半では一番大きなプロジェクトだったように思う。数学苦手なのだけれど面白いなと思えたところが地味な収穫かもしれない。

スペアナ最高に楽しい。でも最近高周波回路やってなくてあんまり起動してない……

キーボード関係

ヤパチーからの流れで自作して、builderscon で発表させてもらった。今年後半の主なプロジェクトとなった。結果的には、カンファレンスできっかけをもらってカンファレンスのトークに還元した感じになって主観的には良かった。

その他組み込みや電子工作

ESP8266 結構触ってたなという感じ。安くなると夢が広がる。

ebay とかで買ったやつをとにかく動かすシリ〜ズみたいなのをやってた。意外と得るものが多い。そのときそのときで触ってる環境で動かしてるのでプラットフォームに一貫性がない。

ioctl を Ruby から呼ぶのを割とやってて、書いてないけどこれ以外にも SPI を Ruby でやるとかもやってたりした。Ruby は IO 処理書くのがかなり楽なのと全体的に可読性が良く、ビルトインメソッドが充実しているので「パフォーマンスはともかく理解しやすいコードを書く」みたいなときに Ruby で書くことが多い。

gem とか使わないとダメになってくると他の言語に逃げがち。gem、クオリティ低いものが大量にあって、どれを使うべきか?自分で実装すべきか?とか悩んでるうちに1日終わる。テキトーに動かしたいときはほんと時間の無駄。

消費電力可視化もいろいろやってみたが、今のところ実用に至ってない。

急に C++ と親密になって好きになった。組込みなら絶対 C++ で書きたい。

mbed 関係。結構さわったしコードも読んだ。

GPS DO を途中までつくった感じ。完成してない (必要性の無さに気付いて、やる気をうしなってしまった)

CNC / PCB Milling 関係

いろんなことで、かなり役に立っている。元はとったな〜というぐらいの気分でいる。grbl からmachinekit (beaglebone black) への移行が主なトピック。machinekit を収める筐体作りがまだ途中。

知見はかなり溜ってきた。

3D モデリング

Fusion 360 導入で可能性がとてもひろがった。全く新しい分野の技術習得という意味では今年一番価値があったかもしれない。拘束によって作図するのはとても気持ちいい。

3D プリンタがない (置く場所がない) のが悩み。欲しんだけどなあ。

その他

全く分類できないもの

cmake はプログラミング言語ってことはわかったけど結局役に立ってない。相変わらず他人の書いた CMakefile.txt は読む気がしない。

  1. トップ
  2. tech
  3. 今年やったことを自分で承認する