2016年 08月 26日

BLE Nano (nRF51822) のドライブ能力を外付け部品なしで拡張する

仕様に書いてあるが標準だと 0.5mA になっている。これは超高輝度LEDなら直接光るかもしれない。電源電圧がそもそも低いので青色とかはやめたほうがよさそう。

なお、設定を変えると最大3ピンまで5mAのドライブ能力に拡張することができる。これは特にピンの制約はなくて、どのピンでも可能なようだ。LEDぐらいしか駆動するものがないのなら、LED ピンのドライブ能力を拡張しておくと安心できる。

設定方法は例えば以下の通りで、

	NRF_GPIO->PIN_CNF[PIN_STATUS_LED] =
		(NRF_GPIO->PIN_CNF[PIN_STATUS_LED] & ~GPIO_PIN_CNF_DRIVE_Msk) |
		(GPIO_PIN_CNF_DRIVE_H0H1 << GPIO_PIN_CNF_DRIVE_Pos);

PIN_CNF の DRIVE を変更すれば良い。この例だとソースもシンクも5mAになる。ソースとシンクは別々に設定可能。

なお mbed 環境で DigitalOut とかしている場合 PIN_CNF レジスタは変更済みなので、必要ないところは上書きしないように注意する必要がある。

mbed + BLE Nano で FOTA (DFUService) を使うには?

nRF51 での FOTA の仕組み

DFUService というのが mbed の BLE_API だと提供されていて勘違いしたけど、これは実際のファームウェア書きこみ処理は一切行わない。これがやっていることは bootloader を起動するということだけだ。

FOTA の仕組みとしては

  1. クライアントは DFUService に対してリクエスト
  2. DFUService はアプリケーション抜けて bootloader として再起動する
  3. クライアントは再度 DFU を見つけて通信を行う
  4. bootloader はBLE経由でデータを受けとってFlashに書きこむ
  5. bootloader はアプリケーションを起動する

という感じですすむ。

mbed 環境でのやりかた

まず、コンパイル済みの bootloader が必要で、これを USB 経由で書きこむ。これで準備完了になるので、これ以降は FOTA だけで書く必要がある。USB 経由で書きこむと bootloader を上書きしてしまうので、FOTA は無効になる 。

bootloader はどれを使うか

https://github.com/RedBearLab/nRF51822-Arduino/tree/S130/bootloader

Arduino IDE 経由で書きこめる bootloader になっているが、FOTA の機能もついている。BLE Nano だとこれ使っておけば良さそう。他のも使ってみたがこれだけ動いた。

mbed からデフォルトでダウンロードされる hex は書けない

ただ、上記 bootloader.hex を書きこんでも、mbed のオンラインコンパイラでコンパイル・リンクして生成される hex ファイルでは基本的に書きこめずに失敗する。これは mbed 環境でコンパイルした場合、親切にも SoftDevice などをマージした状態で hex を作ってくれるから。しかし FOTA するのはアプリケーションの部分だけなので、余計な部分を取り除く必要がある。

これは nRF51_OTA_strip.py を使えばできる。単に引数に入力と出力を与えればアプリケーション部分だけの hex ファイルを吐いてくれる。

ここを変えれば FOTA 版が落とせるみたいです。気付かなかった。ここで FOTA 版を選択すると、DFUService は自動的に組込まれてコンパイルされます。

書きこみかた

できた hex ファイルをなんとかして Android か iPhone に転送する。Google Drive に突っ込むのがてっとり早い。

そして nRF Toolbox を使って DFU をする。このとき、Init packet がどうたらというダイアログがでるが No を選択する。

Device を選択して Upload をタップすれば DFU がはじまる。結構時間がかかる。

bootloader のソースコードは?

https://github.com/ARMmbed/nrf5x-dfu-bootloader

たぶんこれがそれっぽい。ビルドしてないので確認はしてない。

OS X で DFU できないの?

公式ツールは Android / iPhone だけなので、できない。

サードパーティで作ってる人がいる。https://github.com/jeremysf/nrfDFU が、手元だとうまく動かすことができなかった。追試が必要。

2016年 08月 25日

pcb2gcode へのプルリク

ちっちゃな変更を3つほど送った。

add error for positive --zcut by cho45 · Pull Request #46 · pcb2gcode/pcb2gcode · GitHub

なんか pcb2gcode を実行したら刺さるので、こまったなあと思ったら設定ミスがわかった。pcb2gcode 側でせめてwarningぐらい出せや、と思ってかっとなって書いたプルリク。

Milldrill diameter (--milldrill-diameter option) by cho45 · Pull Request #47 · pcb2gcode/pcb2gcode · GitHub

メモ書き:KiCAD + pcb2gcode で pcbmilling | tech - 氾濫原 のとき書いたパッチ。pcb2gcode は --milldrill オプションをつけるとエンドミルを使ってすべての穴をあけることができる。たとえばφ0.8mm 以上の穴しかないなら、φ0.8mmのエンドミルで全ての穴をあけることができる。

ただ、このとき使われるオプションが --cutter-diameter だった。このオプションは外形カット時に使われるオプションなので、外形カットとドリルのときとでエンドミル径を変えることができなかった。

このパッチで --milldrill-diameter として穴をあけるときのエンドミル径を上書きできるようになった。

Clearly specify X/Y to G2. by cho45 · Pull Request #48 · pcb2gcode/pcb2gcode · GitHub

grbl だと G2 に X/Y がない場合、たんに無視されるという挙動をして絶望した。実際に機械を動かして穴をあけてから「あれ? ちゃんと開いてないぞ?」と気付いたので、目の前には加工途中の基板があった。原点がずれるとやっかいなので後日にすることもできず、勘でパッチを書いたら動いてくれた。grbl のコードも pcb2gcode のコードも手元にクローンしていてよかった……


ここまで既にマージ済み。めんどくさいコントリビューションルールもなく、非常にレスポンスはやくレビューしてくれて、良かった。

あとこうやってプルリク送った経緯と書いておくのは良い気がするので送ったときには書いていきたい。

BLE Nano (nRF51822) でどうしても 1mA 以上電流食うぞというとき

BLE Nano をあいかわらず触っている。どうしても消費電流の削減ができず3日ぐらい悩んだので、参考までに「どうすれば効率よく消費電流を削減できそうか」をしるす。

ドキュメントを良くよむこと

nRF51822_PS v3.1.pdf と nRF51_Series_Reference_manual v3.0.pdf というのが主要なドキュメントになる。前者には nRF51822 特有のことがら全般が書かれていて、こちらに消費電力や、ピンごとの物理仕様が書いてある。後者は nRF51 シリーズのシステムのドキュメントになっており、レジスタ仕様とかが書いてある。

消費電力の観点で考えると、まず nRF51822_PS v3.1.pdf に一通り目を通して、どの回路がどのぐらいの電力消費をするかを把握しておくと良い。

当然支配的なところから解決しないとどうしよもないので、大きいところをとりあえずおさえる。具体的には

  • 16MHz HFCLK (クロックだけで約1mAぐらい食う)
  • CPU (起きているときは 4mA ぐらい食う)
  • Radio (送信出力ごとに最大電流が異なる 5.5〜16mA。かなり大きく見えるが BLE の場合送信にかける時間はかなり短いので、平均的には支配率はそれほど高くない)

スリープ中に 16MHz のクロックを切るためには

タイトルの 1mA という数字は HFCLK の消費のことを想定している。このクロックは必要ならば動くという挙動をする。CPU が動いているなら必ず動いている。

大事なのは以下の表 (nRF51822_PS v3.1.pdf から引用)

ここで HFCLK に依存しているブロックがスリープ中に一切ないようにしなければならない。nRF51_Series_Reference_manual v3.0.pdf のほうにはどのペリフェラルが HFCLK に依存しているかは書いてないので、この表はとても大事。

だいたいのブロックは必要なときだけ有効にして動かす系だけど、UART や TWI のようにだいたいオンにしてるみたいなペリフェラルもスリープ前に明示的にオフにする必要がある。というよりは必要なときだけオンにするという使いかたのほうが安全。

TWI なら

NRF_TWI0->ENABLE = TWI_ENABLE_ENABLE_Enabled << TWI_ENABLE_ENABLE_Pos;
do_something();
NRF_TWI0->ENABLE = TWI_ENABLE_ENABLE_Disabled << TWI_ENABLE_ENABLE_Pos;

UART なら

NRF_UART0->ENABLE = (UART_ENABLE_ENABLE_Enabled << UART_ENABLE_ENABLE_Pos);
NRF_UART0->TASKS_STARTTX = 1;
NRF_UART0->TASKS_STARTRX = 1;
// dummy send to wakeup...
NRF_UART0->PSELTXD = 0xFFFFFFFF;
NRF_UART0->EVENTS_TXDRDY = 0;
NRF_UART0->TXD = 0;
while (NRF_UART0->EVENTS_TXDRDY != 1);
NRF_UART0->PSELTXD = tx;

do_something();

while (NRF_UART0->EVENTS_TXDRDY != 1);

uint32_t tx = NRF_UART0->PSELTXD;

NRF_UART0->TASKS_STOPTX = 1;
NRF_UART0->TASKS_STOPRX = 1;
NRF_UART0->ENABLE = (UART_ENABLE_ENABLE_Disabled << UART_ENABLE_ENABLE_Pos);

みたいな感じになる。UART はなんかバグってるのかよくわからないけど、ダミーで一回書かないとちゃんと復帰しなくてこまった。mbed のライブラリにも同様のことが書いてある。

ペリフェラル以外

書きこみインターフェイス (デバッガ) で書きこんだ直後のプログラムはデバッグモードで動いていて、この状態だと上記と同じように 16MHz とデバッガ用の回路が動くようで、消費電力がまったく減らない。

この状態から抜けてノーマルモードで起動するには

  • デバッガを切断したうえで、全ての電源供給をやめて、再度電源を入れなおす
  • デバッガ起動中でもリセットピンでリセット可能にして、デバッガを切断するとともにピンリセットをかける

あたりがある。前者は若干めんどうなので、後者の方法のほうがおすすめ。

// Enable Pin-reset on DEBUG mode
NRF_POWER->RESET = 1;

と main の冒頭あたりに書いておくと、デバッグモード中でもピンリセットがかけられる。「ピンリセットってどのピン?」と思うかもしれないが、SWDIO が nRESET と共用になっているので、書きこみ気から SWDIO/SWDCLK を抜いて GND に一瞬つなげばノーマルモードで起動するようになる。

BLE Nano 固有

BLE Nano は P0_19/D13 に LED がついてる。この LED は VDD に繋っており、負論理で光る。なので、この LED を消したいときは明示的に PullUp するか出力に設定して HIGH にする必要がある。

DigitalIn unused_p0_19(P0_19, PullUp);

備考

ちなみに英語で検索するときは nRF51 power consumption とかでググるのが良いです。

2016年 08月 13日

メモ書き:KiCAD + pcb2gcode で pcbmilling

とりあえずメモだけ

Eagle の場合は pcb-gcode を使えばよかったが、これは Eagle の ULP で書かれているので KiCAD には使えない。pcb2gcode はガーバーファイルから直接 gcode に変換するのでどちらでも使える。

レイアウト

KiCAD 書き出し


pcb2gcode

以下のパッチのブランチで実行

https://github.com/pcb2gcode/pcb2gcode/pull/47

millproject

# Use standard mm
metric=true
metricoutput=1

# front=
back=Main-B.Cu.gbr
outline=Main-Edge.Cuts.gbr
drill=Main.drl

back-output=back.gcode
outline-output=outline.gcode
drill-output=drill.gcode

# https://github.com/chrysn-pull-requests/pcb2gcode/blob/graphical-documentation/man/options.svg
zwork=-0.1
zsafe=1
mill-feed=500
mill-speed=10000
mill-vertfeed=200
offset=0.07679491924311227
#offset=10
extra-passes=1

zdrill=0.9
zchange=20
drill-feed=100
drill-speed=10000
milldrill=true
# milldrill のときの直径 パッチが必要
milldrill-diameter=0.8

# for grbl
nog81=true

# 外形カット時のミル直径
cutter-diameter=1
zcut=-1
cut-feed=500
cut-speed=10000
cut-infeed=1
cut-side=back

optimise=true
zero-start=true
dpi=1000

offset には削るエンドミルの半径を入れる。たとえば 30° 0.1mm のVカッターを -0.1mm で掘る (zwork=-0.1) にする場合 φ0.153mm なので、offset=0.0767 にする。

パッチのメモ

各 gcode を --zero-start で別々に生成すると原点がずれる。しかも --cutter-diameter を変えると外形レイヤーの基準がかわってしまう。

milldrill の場合 drill 系のオプションは無意味で、全て cutter 系のオプションが適用される。

milldrill のときの mill の直径は cutter-diameter と共用のようで、外形カットと穴開けで別の直径のエンドミルにできない…

KiCAD の時点で外形を大きくしておくか、パッチ書くしかない。→ 書いた

結果

pcb2gcode は isolate な部分を全て削る設定にすることができない(と思う)。なので孤立した銅箔が生成されてしまう。 extra-passes=100 とかにすればできるだけ全て削れるっぽい。

offset を大きく設定すると voronoi アルゴリズムが有効になり、孤立した部分をなくせる (孤立した部分は近くの配線に吸収される)。配線というよりは銅箔面の領域分割という感じになる。PCB っぽくはないけどおもしろい。

まだこれは試してない。

2016年 08月 12日

Error:java: cannot access org.apache.http.annotation.Immutable class file for org.apache.http.annotation.Immutable not found

http://www.apache.org/dist/httpcomponents/httpcore/RELEASE_NOTES-4.4.x.txt

httpcore-4.4.5 (2016-06-08) では 4.4.4 ではあったアノテーションが削られています。

Please note the following annotations originally based on CC-BY licensed source have been removed 
in this release:


org.apache.http.annotation.GuardedBy
org.apache.http.annotation.Immutable
org.apache.http.annotation.NotThreadSafe
org.apache.http.annotation.ThreadSafe

httpclient 4.5.1 や 4.5.2 はこれらのアノテーションを使ってるので、うっかり 4.4.5 が入ると死ぬようです。

以下のようにバージョン指定しました。

compile('org.apache.httpcomponents:httpcore:4.4.4')
compile('org.apache.httpcomponents:httpclient:4.5.2')

1時間ぐらいハマった。Java は辛い。

2016年 08月 09日

スマートメータから瞬間消費電力を読むRubyのコード

スマートメータのBルートサービスで Wi-SUN モジュールを使って瞬間消費電力を読み出す | tech - 氾濫原 にひき続き Wi-SUN モジュール ROHM BP35A1 と ECHONET Lite プロトコルを使い、スマートメータから値を取得するサンプルです。

前回のコードはさすがにちゃんと動かなすぎるものなので、多少まともにしたものを書きました。一応16時間ぐらい動かしても止まることなく動く感じです。

連続して動かす場合大事なところ

  • UDP 送信時の失敗処理をちゃんとやること
  • タイムアウト処理をちゃんとやること
    • たとえこちらからの UDP の送信に成功しても、UDPパケットがこちらに必ず受信できる保証はない

途中に環境変数で分岐していますが、片方はテスト用のコードです。Wi-SUN のスキャンが結構時間がかかってイライラするので、想定問答をシミュレーションしています。ちゃんとテスト化したほうがいいんですが、長時間実際に動かしてみるほうが有益だと思ったので限られた時間でそこまでやってません。

#!/usr/bin/env ruby -v

require 'stringio'

module ECHONET_Lite
	EHD1 = 0b00010000
	EHD2_DEFINED = 0x81
	EHD2_ANY = 0x82

	class ParseError < Exception
	end

	def self.parse_frame(frame)
		ret = Frame.parse(frame)
		unless ret.valid?
			raise ParseError.new("not an ECHONET Lite frame")
		end
		ret
	end

	Frame = Struct.new(:ehd1, :ehd2, :tid, :edata) do
		def self.parse(frame)
			ret = self.new(*frame.unpack("CCna*"))
			if ret.valid? && ret.format_defined?
				ret.edata = EDATA.parse(ret.edata)
			end
			ret
		end

		def valid?
			ehd1 == EHD1
		end

		def format_defined?
			ehd2 == EHD2_DEFINED
		end

		def format_any?
			ehd2 == EHD2_ANY
		end

		def pack
			[ehd1, ehd2, tid].pack("CCn") + edata.pack
		end
	end

	EDATA = Struct.new(:seoj, :deoj, :esv, :opc, :properties) do
		def self.parse(edata)
			ret = self.new(*edata.unpack("a3a3CCa*"))
			ret.seoj = EOJ.parse(ret.seoj)
			ret.deoj = EOJ.parse(ret.deoj)

			props = []
			StringIO.open(ret.properties) do |io|
				ret.opc.times do
					epc, pdc = *io.read(2).unpack("CC")
					edt = io.read(pdc)
					props << Property.new(epc, pdc, edt)
				end
			end
			ret.properties = props

			ret
		end

		def pack
			seoj.pack + deoj.pack + [esv, opc].pack("CC") + properties.map {|i|
				i.pack
			}.join
		end
	end

	EOJ = Struct.new(:class_group_code, :class_code, :instance_code) do
		def self.parse(eoj)
			self.new(*eoj.unpack("CCC"))
		end

		def pack
			to_a.pack("CCC")
		end
	end

	Property = Struct.new(:epc, :pdc, :edt) do
		def pack
			self.pdc = edt.length
			[epc, pdc].pack("CC") + edt
		end
	end
end

require 'thread'
class SKSTACK_IP
	EVENT_RECV_NS = 1
	EVENT_RECV_NA = 2
	EVENT_RECV_ECHO = 5
	EVENT_COMPLETED_ED_SCAN = 0x1F
	EVENT_RECV_BEACON = 0x20
	EVENT_UDP_SENT = 0x21
	EVENT_COMPLETED_ACTIVE_SCAN = 0x22

	EVENT_PANA_ERROR = 0x24
	EVENT_PANA_COMPLETED = 0x25
	EVENT_RECV_SESSION_CLOSE = 0x26
	EVENT_PANA_CLOSED = 0x27
	EVENT_PANA_TIMEOUT = 0x28
	EVENT_SESSION_EXPIRED = 0x29
	EVENT_SEND_LIMIT = 0x32
	EVENT_SEND_UNLOCK = 0x33

	def initialize(port)
		@event_callbacks = {}

		@port = port
		@port.set_encoding(Encoding::BINARY)

		@rest = nil

		@queue = Queue.new
		@read_thread = Thread.start do
			Thread.current.abort_on_exception = true
			buffer = ""
			while true
				# need to know command name preceded by whole line
				# because there is ERXUDP/ERXTCP which include length and any binary bytes.
				c = @port.getc
				if c.nil?
					raise "unexpected IO closed"
				end
				buffer << c
				case c
				when ' ', "\r"
					command = buffer.sub(/[\r ]$/, '')
					case command
					when "ERXUDP"
						event = {}
						event[:sender]    = @port.gets(" ").sub(/\s+$/, '')
						event[:dest]      = @port.gets(" ").sub(/\s+$/, '')
						event[:rport]     = @port.gets(" ").sub(/\s+$/, '').unpack("n")[0]
						event[:lport]     = @port.gets(" ").sub(/\s+$/, '').unpack("n")[0]
						event[:senderlla] = @port.gets(" ").sub(/\s+$/, '')
						event[:secured]   = @port.gets(" ").sub(/\s+$/, '')
						datalen           = @port.gets(" ").sub(/\s+$/, '')
						event[:data]      = @port.read(datalen.to_i(16))
						@port.read(2) # ignore crlf
						callback_event(:ERXUDP, event)
						buffer.clear
					when "ERXTCP"
						event = {}
						event[:sender] = @port.gets(" ").sub(/\s+$/, '')
						event[:rport]  = @port.gets(" ").sub(/\s+$/, '')
						event[:lport]  = @port.gets(" ").sub(/\s+$/, '')
						datalen = @port.gets(" ").sub(/\s+$/, '')
						event[:data]   = @port.read(datalen.to_i(16))
						@port.read(2) # ignore crlf
						callback_event(:ERXTCP, event)
						buffer.clear
					when "EPONG"
						event = {}
						event[:sender] = @port.gets("\n").sub(/\s+$/, '')
						callback_event(:EPONG, event)
						buffer.clear
					when "ETCP"
						event = {}
						event[:status] = @port.gets(" ").sub(/\s+$/, '')
						if event[:status] == "1"
							event[:handle] = @port.gets(" ").sub(/\s+$/, '')
							event[:ipaddr] = @port.gets(" ").sub(/\s+$/, '')
							event[:rport] = @port.gets(" ").sub(/\s+$/, '')
							event[:lport] = @port.gets("\n").sub(/\s+$/, '')
						else
							event[:handle] = @port.gets("\n").sub(/\s+$/, '')
						end
						callback_event(:EPONG, event)
						buffer.clear
					when "EADDR", "ENEIGHBOR"
						# ignore
					when "EPANDESC"
						event = {}
						@port.gets("\n") # ignore
						event[:channel]      = @port.gets("\n")[/Channel:(\S+)/, 1]
						event[:channel_page] = @port.gets("\n")[/Channel Page:(\S+)/, 1]
						event[:pan_id]       = @port.gets("\n")[/Pan ID:(\S+)/, 1]
						event[:addr]         = @port.gets("\n")[/Addr:(\S+)/, 1]
						event[:lqi]          = @port.gets("\n")[/LQI:(\S+)/, 1]
						event[:pair_id]      = @port.gets("\n")[/PairID:(\S+)/, 1]
						p event
						callback_event(:EPANDESC, event)
						buffer.clear
					when "EEDSCAN"
						@port.gets("\n") # ignore
						_rssi = @port.gets("\n")
					when "EPORT"
						@port.gets("\n") # ignore
						6.times do
							_udp = @port.gets("\n") # ignore
						end
						@port.gets("\n") # ignore
						4.times do
							_tcp = @port.gets("\n") # ignore
						end
						@port.gets("\n") # "OK" ignore
					when "EHANDLE"
						@port.gets("\n") # ignore
						while line = @port.gets("\n")
							line.chomp!
							break if line == "OK"
						end
					when "EVENT"
						num, sender, param = *@port.gets("\n").sub(/\s+$/, '').split(/ /)
						event = {
							num: num,
							sender: sender,
							param: param
						}
						callback_event(:EVENT, event)
						buffer.clear
					when "EVER"
						event = {}
						event[:version] = @port.gets("\n").sub(/\s+$/, '')
						callback_event(:EVER, event)
						buffer.clear
					when "EAPPVER"
						event = {}
						event[:version] = @port.gets("\n").sub(/\s+$/, '')
						callback_event(:EAPPVER, event)
						buffer.clear
					else
						# do nothing
					end
				when "\n"
					# event 以外
					line = buffer.chomp
					@queue << line
					buffer.clear
				end
			end
		end
	end

	def command(string)
		@port.write(string + "\r\n")
		res = @queue.pop
		if string.split(/ /)[0] == res.split(/ /)[0] # ignore echoback
			res = @queue.pop
		end
		res
	end

	def on(name, &block)
		(@event_callbacks[name.to_sym] ||= []) << block
	end

	private
	def callback_event(name, event)
		(@event_callbacks[name.to_sym] || []).each do |cb|
			cb.call(event)
		end
	end
end

require 'logger'
require 'timeout'
class SmartMeterController
	def initialize
		@logger = Logger.new($stdout)
	end

	def start(io, opts)
		@stack = SKSTACK_IP.new(io)
		@events = Queue.new
		@stack.on(:EVENT) do |e|
			@logger.debug("EVENT %p" % e)
			@events << e
		end
		@epandesc = nil
		@stack.on(:EPANDESC) do |e|
			@logger.debug("EPANDESC %p" % e)
			@epandesc = e
		end
		@transactions = {}
		@stack.on(:ERXUDP) do |e|
			@logger.info("ERXUDP %p" % e)
			begin
				frame = ECHONET_Lite.parse_frame(e[:data])
				if transaction = @transactions.delete(frame.tid)
					transaction.call(frame)
				end
			rescue ECHONET_Lite::ParseError
				@logger.info("Not an ECHONET Lite frame")
			end
		end

		@stack.on(:EVER) do |e|
			@logger.info("EVER %p" % e)
		end
		@stack.on(:EAPPVER) do |e|
			@logger.info("EAPPVER %p" % e)
		end

		@stack.command("SKRESET") == "OK" or raise
		@stack.command("SKVER") == "OK" or raise
		@stack.command("SKAPPVER") == "OK" or raise
		@stack.command("SKSREG SFE 0") == "OK" or raise
		@logger.info("Setting ID and Password")
		@stack.command("SKSETPWD C #{opts[:PASS]}") == "OK" or raise
		@stack.command("SKSETRBID #{opts[:ID]}") == "OK" or raise

		while true
			@logger.info("Scanning device...")
			@stack.command("SKSCAN 2 FFFFFFFF 6")

			while e = @events.pop
				if e[:num].to_i(16) == SKSTACK_IP::EVENT_COMPLETED_ACTIVE_SCAN
					@logger.info("Scan Completed")
					break
				end
			end
			if @epandesc
				break
			end
			@logger.info("Device not found... retrying...")
			sleep 1
		end

		@logger.info("Device found %p" % @epandesc)

		@logger.info("Getting IPv6 Address from MAC Address (%p)" % @epandesc[:addr])
		@ipv6_addr = @stack.command("SKLL64 #{@epandesc[:addr]}")

		@logger.info("Setting Channel and Pan ID")
		@stack.command("SKSREG S2 #{@epandesc[:channel]}") == "OK" or raise
		@stack.command("SKSREG S3 #{@epandesc[:pan_id]}") == "OK" or raise

		@logger.info("Starting PANA")
		@stack.command("SKJOIN #{@ipv6_addr}") == "OK" or raise
		while e = @events.pop
			case e[:num].to_i(16)
			when SKSTACK_IP::EVENT_PANA_COMPLETED
				break
			when SKSTACK_IP::EVENT_PANA_ERROR
				raise "pana error"
			end
		end
		@logger.info("PANA Completed")

		@tid = 0
	end

	def retrieve_power
		@tid += 1

		tid = @tid

		q = Queue.new
		@transactions[tid] = proc {|frame|
			q << frame
		}

		frame = ECHONET_Lite::Frame.new(
			ECHONET_Lite::EHD1,
			ECHONET_Lite::EHD2_DEFINED,
			@tid,
			ECHONET_Lite::EDATA.new(
				ECHONET_Lite::EOJ.new(0x05, 0xFF, 0x01),
				ECHONET_Lite::EOJ.new(0x02, 0x88, 0x01),
				0x62,
				1,
				[
					ECHONET_Lite::Property.new(
						0xe7,
						0x00,
						""
					)
				]
			)
		)

		handle = 1
		port_num = 3610
		sec = 1
		data = frame.pack
		p [:packed, data]
		@stack.command("SKSENDTO %s %s %04X %s %04X %s" % [
			handle,
			@ipv6_addr,
			port_num,
			sec,
			data.length,
			data
		])

		while e = @events.pop
			if e[:num].to_i(16) == SKSTACK_IP::EVENT_UDP_SENT
				unless e[:param].to_i(16) == 0 # success
					return nil
				end
				break
			end
		end

		ret = nil
		begin
			Timeout.timeout(5) do
				ret = q.pop
			end
		rescue Timeout::Error
			@logger.info "UDP Response Timeout"
			@transactions.delete(tid)
		end
		ret
	end
end

io = nil
if ENV["PORT"].nil?
	require 'socket'
	s1, s2 = Socket.pair(:UNIX, :STREAM, 0)
	Thread.start do
		while l = s2.gets
			l.chomp!
			case l
			when "SKVER"
				s2 << "SKVER\r\n"
				s2 << "EVER 1.2.10\r\n"
				s2 << "OK\r\n"
			when "SKSREG SFE 0"
				s2 << "SKSREG SFE 0\r\n"
				s2 << "OK\r\n"
			when /^SKSETPWD/
				s2 << "OK\r\n"
			when /^SKSETRBID/
				s2 << "OK\r\n"
			when "SKSCAN 2 FFFFFFFF 6"
				s2 << "OK\r\n"
				s2 << "EVENT 20 FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX\r\n"
				s2 << "EPANDESC\r\n"
				s2 << "  Channel:2F\r\n"
				s2 << "  Channel Page:09\r\n"
				s2 << "  Pan ID:A0E6\r\n"
				s2 << "  Addr:001C64000357XXXX\r\n"
				s2 << "  LQI:84\r\n"
				s2 << "  PairID:00AXXXXX\r\n"
				s2 << "EVENT 22 FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX\r\n"
			when /^SKLL64/
				s2 << "FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY\r\n"
			when /^SKSREG S2/
				s2 << "OK\r\n"
			when /^SKSREG S3/
				s2 << "OK\r\n"
			when /^SKJOIN/
				s2 << "OK\r\n"
				s2 << "EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 02\r\n"
				s2 << "EVENT 02 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY\r\n"
				s2 << "ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 02CC 02CC 001C64000357XXXX 0 0028 (����O�y\r\n"
				s2 << "EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00\r\n"
				s2 << "ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 02CC 02CC 001C64000357XXXX 0 0068 h����O�z$�r%^a�;H)��#L�8�8/�4+�u\-����&ѨSM00000099021000000000000000AXXXXX\r\n"
				s2 << "EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00\r\n"
				s2 << "ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 02CC 02CC 001C64000357XXXX 0 0054 T����O�{;�;/��4+�u\-����&Ѩ��?s�����r��2���0��D�R��\r\n"
				s2 << "EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00\r\n"
				s2 << "ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 02CC 02CC 001C64000357XXXX 0 0058 X����O�|�  Q�{��(F���.�[�<\r\n"
				s2 << "EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00\r\n"
				s2 << "EVENT 25 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY\r\n"
			when /SKSENDTO/
				s2 << "ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 0E1A 0E1A 001C64000357XXXX 1 0012 \x10\x81\x00\x01\x02\x88\x01\x05\xFF\x01r\x01\xE7\x04\x00\x00\x04Z\r\n"
			end
		end
	end
	io = s1
else
	require 'serialport'

	begin
		io = SerialPort.new(
			"/dev/tty.usbserial-A500YQPG",
			115200,
			8,
			1,
			0
		)
	rescue Errno::EBUSY
		sleep 1
		retry
	end
end

c = SmartMeterController.new
c.start(io, {
	ID: "0000 00XX 0XXX 0000 0000 0000 XXXX XXXX".gsub(/ /, ''),
	PASS: "XXXX XXXX XXXX".gsub(/ /, ''),
})
loop do
	frame = c.retrieve_power
	unless frame
		puts "failed to get power"
		next
	end
	frame.edata.properties.each do |prop|
		p prop
		if prop.epc == 0xe7 && prop.pdc == 4
			watts = prop.edt.unpack("N")[0]
			p "#{watts} W"
		end
	end
end
2016年 08月 06日

スマートメータのBルートサービスで Wi-SUN モジュールを使って瞬間消費電力を読み出す

とりあえずやってみたレベルですが、Bルートサービスが利用開始できたので瞬間電力を読むところまでやってみました。

  • Bルートサービスの申込
  • Wi-SUN モジュールについて
  • ECHONET Lite について

あたりがポイントです。

Bルートサービス申込から

混んでることはわかっていたのであまり期待せずに申し込んでいます。スマートメータに変更していない状態で申し込んでおり、途中でスマートメータ切替工事が入るパターンです。

関東在住ですので、東京電力パワーグリッドの管轄です。

  1. 6月24日に申込
  2. 6月27日に「お客さま情報の照合を完了し、お申込みを受け付けましたので、お知らせいたします」メールがくる
  3. 7月上旬ぐらいに電気メータ交換について東京電力パワーグリッドから「7月15日〜8月10日の間で工事を実施させて頂きます」というお知らせがポスト投函(郵送ではない)される。「停電いたしません」が「ご在宅の場合で停電の了解がいただける場合は停電工事に変更させていただくことがあります」と書いてあった。立ち会いの必要なし。
  4. 7月27日にメーターの「取替完了のお知らせ」がポスト投函されており、メータが替わっていた。工事業者は関電工
  5. 8月3日にメールで『「電力メーター情報発信サービス(Bルートサービス)」パスワードのお知らせ』がくる
  6. 8月4日に郵便で『「電力メーター情報発信サービス(Bルートサービス)」認証IDお知らせ』がくる

できれば工事を見学をしたかったけど、帰宅したら替わってしまっていた。

備考:申込時に必要な供給地点特定番号について

ウェブの電気家計簿を使っている場合、ログインして以下のURLにアクセスするとわかります。

https://www.kakeibo.tepco.co.jp/dk/wmf/meterInfoReference/

電気家計簿を使ってない場合、検針票に書いてあると思います。

通信モジュール

Wi-SUN モジュールは今のところ個人でも手に入るのはロームのものしかなさそう。News Detail に書いてある通り。技適付きなので心配いらない。

RS Online で BP35A1, BP35A7, BP35A7 - accessories を買おうとしたが入手困難といわれてダメで、BP35A7 - accessories だけ送られてきて辛い。チップワンストップ だと在庫があった…… 個人とは取引しないと書いてあるが無視して個人事業主ということで登録して買った。

BP35A7 は既に廃盤?で、BP35A7A というのがある。BP35A1はそのまま。よくわからないがまとめて入手する方法がすくない。

マザーボードとして BP359C というのがあるが、これはUSBシリアル変換とかがついてるだけなので、あまり必要なさそう。

Amazon でもなぜか取り扱いがあって (マーケットプレイスではなく Amazon.co.jp 販売)

Amazon で買う場合 BP35A7 - accessories は高価すぎるので買わずに別のところで買ったほうがよさそう。BP35A7もなぜか高い。が、在庫探しが面倒なら買ってもいいのではないか…

BP35A1 についているコネクタに対応するコネクタは 20P3.0-JMCS-G-TF という JST のコネクタ。Digikey で 120円ぐらいで売っているので、Digikey を利用する際に買っておけばBP35A7相当品を自作できるが、基板設計とか考えるとやはり面倒なのとそこまで高価ではないので素直に買ったほうが良いでしょう。

どうも結構今だと高いので辛い感じがしますがそのうち安いプロダクトがでると信じたい。

モジュールの使いかた

最低限使うのに必要な配線は

  • VCC
  • GND
  • RXD
  • TXD

だけです。電源電圧は3.3Vなので注意。

UART 経由でコマンドを打てば良いので、モジュールとUSB-UART変換さえあればPCで簡単に試せます。

モジュールの仕様書などは「ROHM Sub-GHzシリーズ」サポートページというところから辿れる。

サンプルも用意されているし、コマンドリファレンスも親切に書いてある。

備考: Wi-SUN モジュールについて

Wi-SUN 自体は国際規格化されているけど、日本のスマートメータ用に産まれたというそもそもの経緯があるので、今のところ日本以外で需要がない。なのでモジュール自体が少ない。

Wi-SUN の認証を通っているのはウェブから一覧でみれる。ECHONET Route B から辿ればよさそう。ロームの以外にもあるはあるけど、手に入らない。

スマートメータからのデータ取得

アプリケーションレイヤーでのプロトコルは ECHONET Lite というものです。Wi-SUN の OSI参照モデルでの関係はWi-SUN ECHONET Profile 準拠通信制御ソフトウェア がわかりやすいです。

BP35A1 モジュールでは物理層からトランスポート層(TCP/UDP)及びセッション層(PANA : Protocol for Carrying Authentication for Network Access)までのサポートがありますので、実際の必要なのはアプリケーション層の実装のみです。

ECHONET Lite

仕様書は普通に公開されていて、しかも日本語です。とりあえず必要なのは「第2部 ECHONET Lite 通信ミドルウェア仕様」で、具体的には ECHONET Lite のフレーム構造を理解していればとりあえず通信できます。

ECHONET Lite は概念的には BLE の GATT に似たような仕組みです。「それぞれの機器のあるプロパティを読み書きする」ことができます。

サンプルコードと実行結果

全体的な処理の流れを Ruby で書いてみました。

このコード自体は例外を考慮しておらず全く応用性はありませんが、プロトコルの理解のためにはある程度有用だと思います。ECHONET Lite 部分に細かくコメントをつけています。

ハマりポイント

  • 接続後に「インスタンスリスト通知」というのがこちらから要求しなくても送られてきました
    • ちゃんと受信フレームをパースしないといけないですがこのコードではやってません
require 'serialport'

begin
	@port = SerialPort.new(
		"/dev/tty.usbserial-A500YQPG",
		115200,
		8,
		1,
		0
	)
rescue Errno::EBUSY
	sleep 1
	retry
end

puts "init"

def @port.send(line)
	$stdout.puts ">> #{line}"
	self.write(line + "\r\n")
end

def @port.wait(val)
	while
		line = self.gets.chomp
		$stdout.puts "<< #{line}"
		if val === line
			return Regexp.last_match
		end
	end
end


@port.set_encoding(Encoding::BINARY)

@port.send "SKVER\r\n"
@port.wait "OK"

# エコーバックをオフに
@port.send "SKSREG SFE 0\r\n"
@port.wait "OK"

# スペース区切りで通知されるが、実際は削除する
ROUTE_B_ID = "0000 0099 0210 0000 0000 0000 00XX XXXX".gsub(/ /, '')
ROUTE_B_PASS = "XXXX  XXXX  XXXX".gsub(/ /, '')

@port.send "SKSETPWD C #{ROUTE_B_PASS}"
@port.wait 'OK'

@port.send "SKSETRBID #{ROUTE_B_ID}"
@port.wait 'OK'

info = {}
@port.send 'SKSCAN 2 FFFFFFFF 6'
info[:sender] = @port.wait(/^EVENT 20 (?<sender>.+)/)[1]
@port.wait(/^EPANDESC/)
info[:channel]      = @port.wait(/^  Channel:(?<channel>.+)/)[1]
info[:channel_page] = @port.wait(/^  Channel Page:(?<channel_page>.+)/)[1]
info[:pan_id]       = @port.wait(/^  Pan ID:(?<pan_id>.+)/)[1]
# MACアドレス
info[:addr]         = @port.wait(/^  Addr:(?<addr>.+)/)[1]
info[:lqi]          = @port.wait(/^  LQI:(?<lqi>.+)/)[1]
info[:pair_id]      = @port.wait(/^  PairID:(?<pair_id>.+)/)[1]
p info
#<<   Channel:2F
#<<   Channel Page:09
#<<   Pan ID:A0E6
#<<   Addr:00XXXXXXXXXXXXXX
#<<   LQI:7D
#<<   PairID:00XXXXXX
@port.wait(/^EVENT 22/)

# macアドレスをipv6 アドレスへ変換
@port.send "SKLL64 #{info[:addr]}"
info[:ipv6_addr] = @port.wait(/[0-9A-F:]+/)

# channel
@port.send "SKSREG S2 #{info[:channel]}"
@port.wait "OK"

# pan id
@port.send "SKSREG S3 #{info[:pan_id]}"
@port.wait "OK"

@port.send "SKJOIN #{info[:ipv6_addr]}"
# 0x25:PANA による接続が完了した
@port.wait(/^EVENT 25/)

@tid = 0
while true

#    (1)Layer4 で UDP(User Datagram Protocol)、Layer3 で IP(Internet Protocol)、
#    を使用する場合
#    各 ECHONET Lite ノードは、それぞれ IP アドレスを持つ。IP アドレスの範囲、
#    取得方法は規定しない。1つの ECHONET Lite フレームは、1つの UDP パケッ
#    トにて転送する。UDP パケットにおける送信先 PORT 番号は、要求・応答・通知
#    等の種別に関わらず、常に 3610 とする。

    handle = 1
    port_num = 3610
    sec = 1 # 暗号化有効・有効でなければ送信しない

    # 上記の通り data は ECHONET Lite フレームとなる
    # ref. https://echonet.jp/wp/wp-content/uploads/pdf/General/Standard/ECHONET_lite_V1_12_jp/ECHONET-Lite_Ver.1.12_02.pdf
    ### ref. https://echonet.jp/wp/wp-content/uploads/pdf/General/Standard/Release/Release_G_revised/Appendix_G_revised.pdf
    @tid += 1
    data = [
        # EHD1 = ECHONET Lite 1 byte
        0b00010000,
        # EHD2 = 既定形式 1 byte
        0x81,
        # TID - トランザクションID 2 bytes
        @tid,

        # EDATA
        
        ## SEOJ - 送信元ECHONET Liteオブジェクト指定 3 bytes
        ### Class Group Code - 管理・操作関連機器クラスグループ
        0x05,
        ### Class Code - コントローラ
        0xFF,
        ### Instance Code
        0x01,

        ## DEOJ - 相手先ECHONET Liteオブジェクト指定 3 bytes
        ### Class Group Code - 住宅・設備関連機器クラスグループ
        0x02,
        ### Class Code - 低圧スマート電力量メータ
        0x88,
        ### Instance Code
        0x01,

        ## ESV - ECHONET Lite サービス 1 byte
        ## プロパティ値読み出し要求
        0x62,

        ## OPC - 処理プロパティ数
        0x01,

        ### EPC - プロパティ - 瞬時電力計測値
        #### 電力実効値の瞬時値を 1W 単位で示す。(結果は 4 bytes singed long)
        0xe7,
        ### PDC - EDTのバイト数
        0x00,
        ### EDT - プロパティデータ
        # なし
    ].pack("CCn CCC CCC C C CC".gsub(" ", ""))

    p data

    @port.send "SKSENDTO %s %s %04X %s %04X %s" % [
        handle,
        info[:ipv6_addr],
        port_num,
        sec,
        data.length,
        data
    ]

    # TODO use datalen to read
    res = @port.wait(/^ERXUDP (?<sender>\S+) (?<dest>\S+) (?<rport>\S+) (?<lport>\S+) (?<senderlla>\S+) (?<secured>\S+) (?<datalen>\S+) (?<data>\S+)/)
    p res
    # まず接続後にインスタンスリスト通知 がくる
    # \x10\x81\x00\x00
    # \x0E\xF0\x01 ノードプロファイル
    # \x0E\xF0\x01 ノードプロファイル
    # \x73 プロパティ値通知
    # \x01 1つ 
    # \xD5
    # \x04 4バイト
    # \x01\x02\x88\x01
    #
    # "\x10\x81\x00\x00\x0E\xF0\x01\x0E\xF0\x01s\x01\xD5\x04\x01\x02\x88\x01"
    p res[:data][-4..-1].unpack("N")
    sleep 3
end

@port.send "SKTERM"
@port.wait "ENT 27"

実行すると以下のようになります。一部アドレスなどは念のため置き換えています。最後のほうに出てくる[1097]が瞬間消費電力です。

init
>> SKVER
<< SKVER
<< EVER 1.2.10
<< OK
>> SKSREG SFE 0
<< SKSREG SFE 0
<< OK
>> SKSETPWD C XXXXXXXXXXXX
<< OK
>> SKSETRBID 000000000000000000000000000000XX
<< OK
>> SKSCAN 2 FFFFFFFF 6
<< OK
<< EVENT 20 FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX
<< EPANDESC
<<   Channel:2F
<<   Channel Page:09
<<   Pan ID:A0E6
<<   Addr:001C64000357XXXX
<<   LQI:84
<<   PairID:00AXXXXX
{:sender=>"FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX", :channel=>"2F", :channel_page=>"09", :pan_id=>"A0E6", :addr=>"001C64000357XXXX", :lqi=>"84", :pair_id=>"00AXXXXX"}
<< EVENT 22 FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX
>> SKLL64 001C64000357XXXX
<< FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY
>> SKSREG S2 2F
<< OK
>> SKSREG S3 A0E6
<< OK
>> SKJOIN FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY
<< OK
<< EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 02
<< EVENT 02 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY
<< ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 02CC 02CC 001C64000357XXXX 0 0028 (����O�y
<< EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00
<< ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 02CC 02CC 001C64000357XXXX 0 0068 h����O�z$�r%^a�;H)��#L�8�8/�4+�u\-����&ѨSM00000099021000000000000000AXXXXX
<< EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00
<< ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 02CC 02CC 001C64000357XXXX 0 0054 T����O�{;�;/��4+�u\-����&Ѩ��?s�����r��2���0��D�R��
<< EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00
<< ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 02CC 02CC 001C64000357XXXX 0 0058 X����O�|�  Q�{��(F���.�[�<
<< EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00
<< EVENT 25 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY


"\x10\x81\x00\x01\x05\xFF\x01\x02\x88\x01b\x01\xE7\x00"
>> SKSENDTO 1 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 0E1A 1 000E ���b�
<<
<< EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00
<< OK
<< ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FF02:0000:0000:0000:0000:0000:0000:0001 0E1A 0E1A 001C64000357XXXX 1 0012 ���s��
#<MatchData "ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FF02:0000:0000:0000:0000:0000:0000:0001 0E1A 0E1A 001C64000357XXXX 1 0012 \x10\x81\x00\x00\x0E\xF0\x01\x0E\xF0\x01s\x01\xD5\x04\x01\x02\x88\x01" sender:"FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY" dest:"FF02:0000:0000:0000:0000:0000:0000:0001" rport:"0E1A" lport:"0E1A" senderlla:"001C64000357XXXX" secured:"1" datalen:"0012" data:"\x10\x81\x00\x00\x0E\xF0\x01\x0E\xF0\x01s\x01\xD5\x04\x01\x02\x88\x01">
[16943105]
"\x10\x81\x00\x02\x05\xFF\x01\x02\x88\x01b\x01\xE7\x00"
>> SKSENDTO 1 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 0E1A 1 000E ���b�
<< ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 0E1A 0E1A 001C64000357XXXX 1 0012 ���r�Z
#<MatchData "ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 0E1A 0E1A 001C64000357XXXX 1 0012 \x10\x81\x00\x01\x02\x88\x01\x05\xFF\x01r\x01\xE7\x04\x00\x00\x04Z" sender:"FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY" dest:"FE80:0000:0000:0000:021D:1290:0003:A4F1" rport:"0E1A" lport:"0E1A" senderlla:"001C64000357XXXX" secured:"1" datalen:"0012" data:"\x10\x81\x00\x01\x02\x88\x01\x05\xFF\x01r\x01\xE7\x04\x00\x00\x04Z">
[1114]
"\x10\x81\x00\x03\x05\xFF\x01\x02\x88\x01b\x01\xE7\x00"
>> SKSENDTO 1 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 0E1A 1 000E ���b�
<<
<< EVENT 21 FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY 00
<< OK
<< ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 0E1A 0E1A 001C64000357XXXX 1 0012 ���r�I
#<MatchData "ERXUDP FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY FE80:0000:0000:0000:XXXX:XXXX:XXXX:XXXX 0E1A 0E1A 001C64000357XXXX 1 0012 \x10\x81\x00\x02\x02\x88\x01\x05\xFF\x01r\x01\xE7\x04\x00\x00\x04I" sender:"FE80:0000:0000:0000:YYYY:YYYY:YYYY:YYYY" dest:"FE80:0000:0000:0000:021D:1290:0003:A4F1" rport:"0E1A" lport:"0E1A" senderlla:"001C64000357XXXX" secured:"1" datalen:"0012" data:"\x10\x81\x00\x02\x02\x88\x01\x05\xFF\x01r\x01\xE7\x04\x00\x00\x04I">
[1097]

実際はイベントや UDP の処理を適切に書く必要があります。ちゃんとやらないとダメなので、適切なライブラリがあると嬉しそうです。

2016年 08月 03日

AMP に対するモヤモヤ

結構前にAMP 対応しようと思ったけどやめたときのメモを掘り起こしてポストしておく。



AMP のチュートリアル的なやつ数回眺めて「なんか(標準として)イマイチだなー」と感じつつ、 Google がサポートするなら一回試すぐらいはしようと思いやってみましたが、対応を見送りました。

AMP のスコープ

  • ページリフローの低減
    • 画像サイズ指定の厳密化とか
  • ブロッキングスクリプトを強制的に不許可
  • 遅くなりそうな機能はとにかくナシ

https://www.ampproject.org/docs/get_started/technical_overview.html 見ると、別に AMP だからできるのだ!っていう機能はない。

AMPには2つの要素が同居している

  1. コンテンツ自体の表示の高速化 (レンダリング負荷の軽減など)
  2. コンテンツをダウンロードしはじめるまでの高速化 (CDN配信とか)

後者はつまり AMP を使っている限り悪意のあるスクリプトのあるページにはなりませんよという保証。

AMP のメリット

  • 検索流入からの表示が早くなる
    • Google (など) にキャッシュされてCDN経由で配信される

AMP のデメリット

  • 普通にロードする限りではたいして早くならない
    • ないし真の static コンテンツに対して AMP JSの実行時間分損をする
  • 専用のHTML・CSSを書く必要がある
    • かなり制約が多い。CSS にも !important はダメとか細かい制約がある
  • マークアップ側にレイアウトという概念がある
    • は?
  • AMP JS で提供される以外の機能は使えない

AMPのメリットはメリットなのか?

検索流入に特化するならメリットになる。Google の CDN 経由で配信されるのが大きい。

しかし、似たような構成 (特にいえばJS無効の) のページのロード時間ってそもそも遅くない。コネクションにかかる時間分の速度向上のために、わざわざ特殊なマークアップで書くメリットはあるだろうか。

AMP は標準になりえるか?

結局 AMP がやってることって「JSなしのページのキャッシュ・プリロード」+「オレサマのJSなら特別に実行してやっても良いぞ」みたいな感じなので、そのために変なカスタムエレメントとか入れてんの? レイアウトまわりにCSS以外の概念入れんの?? という疑念が晴れない。

元々 Cache-Control: public という便利なのがあるのだから、その場合はキャッシュ済みのを CDN から配信するのを優先したらいいだけじゃないか? 既存技術の延長でなんとかできる範囲ではなかったのだろうか? 任意スクリプトってのが最大の問題だけど、AMPならオッケーってのはどうなのか?

ベンダープリフィックスつきのボイラープレートをいちいち書かされるのも意味不明。新しいベンダー出てきたらどうすんの?

カスタムエレメントも、amp-twitter とか amp-facebook とか、どういうつもりで追加してるのかわからないエレメントがたくさんある。いろんなサービスが「カスタムエレメントを定義してくれ」といったら対応コードが無限に増えていくんでしょうか。amp-ad も、メジャーな広告ネットワークをサポートしているけど、そもそもサポートする広告ネットワークを指定されてるのが気にくわない。いろんな無限のアドネットワークがが「ウチにも対応してくれ」といったら対応コードが無限に増えていくんでしょうか。

と、だんだんイライラしてきたのでやめて、別の見方をしてみることにします。

JS フレームワーク AMP

  • Google でインデックスされることが保証されてる
  • 画像の遅延ロードとかはSEO的にどうなの?という不安があったがAMPでは保証される
  • なんとなく便利なインターフェイスがついてて、パフォーマンスが良いことが保証されている

あたりを考えると、変なJSライブラリを使うよりはマシそうな雰囲気があります。この捉えかたで多少心が落ち着きました。

「AMP とは準 static ページを作るためのJSフレームワーク」であって、Google が対応済みという大きなメリットがある! 万歳!!! Google が対応済みってのがとにかく最高!!!

2016年 07月 31日

OS X で KiCAD を使う際の注意点

前提

どのOSでもレンダリングは遅くて、多少乱れるので、OS X だから遅いのかな〜とか考える必要はない。

OS X 固有の話

  • pcbnew / eeschema は一定の大きさまでウィンドウを広げると刺さります
    • GPU のメモリとかに左右されるかもしれません
    • 自分の環境で「ギリギリまで広げられる範囲」を見つける必要があります
    • Retina だとちょっと広げただけで刺さるので、Retina ではない外部ディスプレイを使いましょう。内蔵Retina に限らず、ppi 高いディスプレイだと使いにくいのでダメです……
  • pcbnew を起動したときOpenGL ビューの初期化に失敗します
    • 気にしないで起動してから OpenGL ビューに変えれば普通に動きます
    • または標準ビューにしましょう
  • pcbnew はズームレベルを広げすぎる(俯瞰にしすぎる)と刺さります
    • うっかりマウスホイールを回転しすぎないようにしましょう
    • これが一番困ります

eeschema / pcbnew 共通の罠

  • ホイール使うとポインタが飛ぶ!!
    • 「設定」から「拡大縮小時にカーソルを中心へ移動」のチェックを外します
  • 中クリックしながらドラッグでパンする機能が動かない
    • 「設定」から「画面のパンにマウスの中ボタンを使用」にチェックを入れます
    • 一回ウィンドウの内側を左クリックすると動いたりします
  • フットプリントの関連づけに自分の部品がでない
    • 既存ライブラリにある部品名とかぶっていると出ないことがあります (優先順位による)

pcbnew の罠

  • 描画モードによって機能が変わります
    • 部品を一括で並べかえる機能はデフォルトのビューじゃないと使えません
    • おしのけ配線は OpenGL のビューじゃないと使えません
  • 部品を一括で並べかえる機能は基板外形がないと機能しません
    • チュートリアル的なPDFだと外形なしでやってる風なんですが、できません
  • 配線の undo できない
    • 配線ツールを選択している状態だと undo できないので ESC 連打して通常の選択ツールにします。するとなんと undo できます。
  • 全配線の ripup
    • 標準描画モードにして、全体を選択する。このとき選択対象にワイヤーとviaを入れる。選択されたら削除する
  • 配線を維持してコンポーネントの移動
    • OpenGL ビューだとできません
    • 標準ビューだと回路図エディタと同様に G でできる
  • ここにさらに追記されます