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 は辛い。

  1. トップ
  2. tech
  3. Error:java: cannot access org.apache.http.annotation.Immutable class file for org.apache.http.annotation.Immutable not found

スマートメータの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
  1. トップ
  2. tech
  3. スマートメータから瞬間消費電力を読むRubyのコード

先週火曜日は体調不良で休んだが、子供も熱を出してひきとることになったので、結局完全には休めなかった。

鼻炎と喉がすこし痛いぐらいなので耳鼻科にいって薬をもらっているけど治らない。というか鼻炎と喉はともかく全身倦怠感がひどくてなにもできない。

とりあえずやってみたレベルですが、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 の処理を適切に書く必要があります。ちゃんとやらないとダメなので、適切なライブラリがあると嬉しそうです。

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

dot by dot だと細かすぎると感じることがあるが、だからといって高解像度設定だと狭すぎる。(結局 dot by dot で使ってる)

ということで、5K が本命だなと感じている。

Dell ディスプレイ モニター UP2715K 27インチ/5K/IPS低反射/8ms/MimiDPx1,TwinDPx1/AdobeRGB 99%/USBハブ/スピーカ内蔵/3年間保証 - Dell

Dell

3.0 / 5.0

最近まで 5K は DELL の 27インチが20万ぐらいという感じだったが、いつのまにか HP Z27q 5K というのも出ていて、これがだいぶ安い。

 -

3.0 / 5.0

どちらも AdobeRGB 99% の色域。

今うちのモニタは 27インチ 2560x1440 (AdobeRGB) と 24インチ 4K (sRGB) なので、2560x1440 を 5K に単純に置き換えられそう。なんだけど、現実的には以下の点で難しい。

  • KiCAD みたいな低解像度環境でしか使えないアプリケーションがある
  • そんなに映像出力がない

モニタの進化についていけてない……

結構前に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 が対応済みってのがとにかく最高!!!

  1. トップ
  2. tech
  3. AMP に対するモヤモヤ

シンワ測定(Shinwa Sokutei) ポケットノギス 100mm 19518 - シンワ測定(Shinwa Sokutei)

シンワ測定(Shinwa Sokutei)

5.0 / 5.0

シンワ測定(Shinwa Sokutei) デジタルノギス 大文字 ホールド機能付き 15cm 19975 - シンワ測定(Shinwa Sokutei)

シンワ測定(Shinwa Sokutei)

3.0 / 5.0

デジタルとアナログだと1ケタ精度が違うのだけど、デジタルだとその分取り扱いに気をつける必要があってちょっと面倒だったりする。デジタルノギスは箱に入れて保管しているので、雑に計りたいときは結局雑に使えるアナログしか使わない。そして殆どのケースで精度は必要なくて雑に計ればことたりてしまう。

デジタルノギスを使うのは結局、現物しかないものの寸法を図面に起こすときぐらいしかない。全く使ってないわけではないから無駄とまではいえないけど、そんなに必要ではなかったな、という感想。

あとそもそも買ったデジタルノギスはいまいちすべりが悪くて使いにくい……

前提

どの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 でできる
  • ここにさらに追記されます
  1. トップ
  2. tech
  3. 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 でできる
  • ここにさらに追記されます
  1. トップ
  2. tech
  3. OS X で KiCAD を使う際の注意点

僕はポケモンシリーズを一切プレイしたことがなくて、子供のときはコロコロか何かについてきたポケモンのモンスター一覧を眺めて「進化先が複数あるポケモンとかいるんか! すげーな!」とか思っていただけだった。ゲームボーイを持ってなかったし、買ってもらってまで(お小遣いでは買えないし、交渉するのが大変だった)やるほどではなかった。

それはともかく、Ingress は結局無視してたけど (やりはじめるタイミングがなかった)、ポケモンはやってみることにした。なんといっても世界的ブーム!! 長いものにはマカレロ!!! 同じアホなら踊らにゃ損ソン!!!

住んでいるところのポケスポットをいくつか回ってみたりした。明かに「これはポケモンGoやってるな」っていう人が結構いてすごい。Ingress のことを想うと知的財産ってすごいという感じがする。都心ではないがルアーモジュールが刺さってるスポットも結構あった。

それはともかく、今住んでるところは小学生から断続的に住んでいる土地なので、まぁだいたいどんなスポットがあるかは分かっているつもりだったけど、そんなことなかった。「こんなにお地蔵さんあったのか」みたいな発見はある。面白い。

ゲームシステムの感想

ポケモンGo 良いと思ったのは、ゲーム内のコミュニケーション要素がかなり削られているところ。今のところジムで対戦する以外にゲーム内で他プレイヤーとの接点はない。

ゲームの主目的も「ポケモンの収集」がメインであって、自分だけで完結する。

Ingress はもはや古参と暗黙のルールが作られたネトゲなので、初心者が変なことをするとなんか言われるという負のモチベーションがある。成熟したネトゲには全てを理解している人しかプレイしてはいけないみたいな感じがだいたいあって (変なことをすると「なんでxxしないの?」みたいなことを言われたりとか)、そういうのは初心者プレイヤーを大きく不愉快にさせる。Ingress はもうそういう状態で、もはや Ingress を初める気にならない大きな理由はこれになる。

ポケモンGoはまだ新しいというのもあるが、そもそもシステム的にそういうのがない。原理的に、他人に不利益になるようなことは殆どできない。ポップしたポケモンはその場にいる全ての人で共有して沸くが「取り合い」にはならず、全ての人がボールを投げられる。ジムの取り合いはポケモンの収集には必須要素ではないし、全く関わらずに強いポケモンを育てたりもできる。

背景

KiCAD は1つのプロジェクトにつき1つのボードしか作れない。小さな基板であれば1つの基板にVカットを入れるでいいが、どうしても複数ボードとして設計したい場合に困る。

複数プロジェクトにして回路図をわけると、今度はこの回路図間でコピペが動かないという問題が発生する。KiCAD はプロジェクト間のブロックコピー・ペーストができない。

階層シート

KiCAD は階層シートという、回路図については複数ページに分けて書く機能をサポートする。これは部分的に別の .sch 回路図ファイルとして分離して、プロジェクトルートの回路図から参照するという形をとる。名前の通り、これはツリー上にすることができる。

考えたやりかた

考えかた

  • 全体を管理するプロジェクトとルート回路図を作る
  • 各基板ごとに階層シートとして回路図ファイルを分離する
  • 各基板ごとに別のプロジェクトとルート回路図を作る
  • 各基板のルート回路図から全体を管理するプロジェクトの階層シートを参照する

これで原理的には全体を管理するプロジェクトのルート回路図を開いて各階層シート(モジュール)に入ったり出たりして図を書け、各基板ごとのプロジェクトのルート回路図を開けば各基板ごとだけのネットリストを吐ける。

実際のオペレーション

ルートに何も置かず、それぞれのボードごとに階層シートとして、まず全て設計する。このとき作る回路図を Root.sch とする。階層シートはそれぞれ _module_a.sch とか、あとあと被らないような命名にしておく。

全体ができたら (できる前でもいいけど)、Root.sch をモジュール数分だけコピーする。これは KiCAD 上ではできないので適当にターミナルとかでやる

$ cp Root.sch ModuleA.sch
$ cp Root-cache.lib ModuleA-cache.lib

cache.lib もコピーしないとダメ。

この状態で KiCAD のプロジェクトビューを更新すると、コピーした回路図が見えるので開く。開いてから保存すると勝手に ModuleA.pro が作られて別プロジェクトとなる。ModuleA.sch ではボードに実装したい階層シート以外を削除する。ModuleA.sch はこれで1つのボードに対応する回路図となる。

他のモジュールも同じようにコピーして必要な階層シートだけを残す。

結果的にできる構造

Root.sch
  _module_a.sch
  _module_b.sch
 
ModuleA.sch
  _module_a.sch

ModuleB.sch
  _module_b.sch

それぞれキャメルケースのファイル名の回路図がルート回路図で、_ から始まるスネークケースのファイル名の回路図が階層化回路図になっていて、上記のような参照関係になる。

こうすると、Root.sch で全体を見ながら各階層化回路図に変更をかけたことが、個々のモジュール用回路図にも反映される。ネットリストはモジュールごとの回路図から生成するので、これでボードごとのネットリスト出力ができる (Root.sch に対応する kicad_pcb ファイルは作らない)。

ワークフロー

基本的に Root.sch 開いた状態で階層化回路図を編集する。

これで保存をして閉じて、ModuleA.sch を開くと、編集が反映された状態になっている。この状態でフットプリントの関連付け、及びネットリスト出力をして pcbnew すると、該当する基板の部品だけを出せる。

ネットリスト・pcbnew した後は以下のようになる

Root.sch はあくまで全体管理用なのでネットリストとかは出さない。

.sch 間のコピペ

コピペ対象の回路図が階層化回路図になっていなければならない。なので、全体を見ながらモジュールの構成をしたいときは Root.sch を開いてそれぞれコピペ(ブロックを保存)することになる。

他に方法はないのか

わかりません。普通に全部を吐きだしたネットリストから、うまいこと各階層シートごとのネットリストだけ分離するようなスクリプトを書けばもっと楽に管理できるかもしれません。

標準のネットリストファイルはS式なんですが、ちょっと見た感じだと簡単にはできない気がしました。

  1. トップ
  2. tech
  3. KiCAD で複数ボードプロジェクトを運用する

仕事が忙しくなってくると、同時に趣味もやりたいことがいっぱい出てきて(逃避行動)、主観的な忙しさが指数的に上がっていく感じがする。

id:h2u エルゴマウスの持ちやすさ。単三*2で重いマウス、単四+スペーサーにして軽量化とかはやってた。

http://b.hatena.ne.jp/entry/s/lowreal.net/2016/07/19/1

というコメントがついていて、単4+スペーサーで軽量化というのになるほど!!! と思ったのでやってみました。

【BLUELOTUS】単4形電池を単3形電池に変換 電池変換アダプター 電池スペーサー BL-224-RK (6本) - bluelotus

bluelotus

5.0 / 5.0

Amazonベーシック 充電池 充電式ニッケル水素電池 単4形8個セット (最小容量800mAh、約1000回使用可能) - Amazonベーシック(Amazon Basics)

Amazonベーシック(Amazon Basics)

5.0 / 5.0

これらを買って元からついてきていたアルカリ電池と入れ替えしました。

感想

確かに軽くはなったんですが、本体そのものが思ったより重くてあんまり実感がわきませんでした。しかしじわじわ効いてくると信じたい。

備考

スペーサーのやつはレビューを読むと違う商品が送られてくるパターンがあるみたいです。今回 umiwo というところから買いましたが、ちゃんとしたのが送られてきました。ただ、この販売者はもうこの商品を出品していないようです。

「ちゃんとした」というのは、プラスにもマイナスにもちゃんと電極がついているやつです。電極はスペーサー内で多少カタカタと動くのですが、これにより機器にセットしたとき機器側のバネで抑えつけられることで導通が確保されるようになっているようです。

標題の通りですが HHKB を半分に割ってブチ壊すみたいな話ではないのでご安心ください。

  1. HHKBを2台用意します。
  2. HHKB 2台を横に並べます
  3. Karabiner を入れます
  4. 完成です

通常、キーボードを複数繋いでも、修飾キーは各キーボードごとに独立して管理されます。なので、2台キーボードを繋いで並べたとしても右のキーボードでShiftを押しながら左のキーボードのaを押してAを入力するみたいなことができません。

Karabiner を入れるとこの問題が解決します。インストールするだけで、全てのキーボードで修飾キーが共有されるようになり、同一のキーボード2台があれば左右分割のエルゴノミクスキーボードっぽく使うことができるようになります。

 -

5.0 / 5.0


背景

ErgoDox を見てから左右分割キーボードに対して興味が沸いたので、簡単に試せる方法を探していました。Karabiner の機能ページを見てみたら修飾キーを共有する機能が書いてあったので「これでできるやん」と思いやってみました。HHKB は自宅用と会社用とかで2台持っている人も多いと思うので、意外と試しやすいのではないでしょうか?

あと ErgoDox って結構高価なので、不安なら価値が安定しているHHKB2台買っても良いのではないしょうか。僕は1台は貰いものなので他人のこと言えませんが……

感想

最初の5分ぐらいは同じキー配列にも関わらず、ちょっと頭が混乱していてうまく打てませんでした。少ししたら全く問題なく打てるようにはなりました。しかしパスワードはかなり複雑で特殊な打ちかたをしていたのでうまく打てません。

とりあえず使うにはこれでもいいんですが、使い続けようと思うと会社用と家用に2台ずつHHKBが必要になって大変コスパが悪そうです。また、修飾キー共有をソフトウェアに頼っているので環境が限定されます。

左右分割すればエルゴノミクスなのか?

知るかよ

  1. トップ
  2. tech
  3. HHKB を左右分割エルゴノミクスキーボードにする (OSX)

I2C がうまく動かなくて調べていたら、どうやら UART を使おうとすると競合するようでした。確かにピン配置を見ると CTS/RTS と SDA/SCL はかぶっています。これは事前に知っていましたが、RXD と TXD とはかぶっていないので、問題なく使えると思っていました。

しかし実際は Serial を使おうとすると問答無用で該当ピンが CTS/RTS が設定されるコードがハードコードされています。

serial_api.c

TXD/RXD は外部から渡せるようになっているのに、CTS/RTS は決め打ちで勝手に機能割当されます。なんやねん。

解決方法

Serial を初期化したあと、CTS/RTS の機能割当をはずせば良いようです。すなわち

NRF_UART0->PSELRTS = 0xFFFFFFFFUL;
NRF_UART0->PSELCTS = 0xFFFFFFFFUL;

というようなコードを main を最初あたりに入れておけば良いです。0xFFFFFFFFUL は Disconnected を表す定数です (NC という定数になっていますが型が違うのでリテラルで書いてます)

  1. トップ
  2. tech
  3. BLE Nano + mbed の Serial の実装がつらい感じだった