8K とか、あるいはその周辺で VR ってのは福祉なわけ。映像はまだまだ発展途上なの。歳をとって寝たきりになっても旅行を体験したいでしょう。ただ生きているだけでは辛い。

今の会社は2012年1月1日入社ってことになってるので、今年で6年目になる模様。結構続いてる。

年末の、ちょうどかかりつけの小児科が休みになった翌日から子どもが高熱(一時的に39℃超、その後38℃超)を出し、おさまらないので休日夜間急患診療所に(妻が)連れていったところインフルエンザと診断されてタミフルが処方された。

子どもは2日ぐらいには解熱してやたら元気になったが外出するわけもいかず、急患診療所は3日分しか薬を処方できないのにタミフルは5日間飲み続ける必要があるという制約があったり (年末年始のような長期休暇時には急患診療所に再度行かないといけない)、それが終わっても保育園登園のためにはかかりつけ医に登園許可証を書いてもらう必要がありかなり面倒くさい。

ということで実家に帰ったりする予定をキャンセルし、年末年始は完全にひきこもっておりコンビニぐらいでしか外にでなかった。プラレールの技術力が上がってきた。

とりあえずは大人が発症しなかったのと痙攣が起きなかったのは不幸中の幸い。


タミフルといえば異常行動が有名だけど、うちの子供も若干様子がおかしかった。プラレール組みたてながら悪寒?っぽい動きをしたかと思えば「オバケがいた」と言い出したり、一時的にハイテンションになったりしていた。実際にこれが副作用かどうかは微妙だけど「これがタミフルのチカラか〜」と思った。あとたぶん副作用で下痢をしていた。

さくらのVPSの1GB HDDプランから、2GB SSD プランに移行してしばらく経過した。Google のサーチコンソールで見た感じだと全体的にスピードが上がって、なおかつ安定している。

  1. トップ
  2. tech
  3. VPSのSSD化の効果

接続しようとしても即座に onclose が呼ばれてしまう。しかもこのブラウザの onclose イベントには code プロパティも reason プロパティも存在してなくて原因がわからない。

  • サーバサイドでは 400 を返している
  • wss (TLS 経由) もダメ

備考

Kindle Paperwhite のブラウザはうっかり alert() を無限ループさせると端末再起動しかない。しかし再起動ダイアログが alert() のダイアログの下に出てしまう。

  1. トップ
  2. tech
  3. Kindle Paperwhite のブラウザ(体験版)のWebSocketがおかしい(未解決)

  • 健康になりたい
    • 歩く時間を増やす

ref.

失敗例

↑ つまり失敗しているということである。具体的なことなく「なりたい」では目標は達成できないという例

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

  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. 今年やったことを自分で承認する

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

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

$  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 と言われる

ウェブページから 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

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 をセットアップする

寝室に置いてみたいので 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 を読む