スマートメーターの情報を最安ハードウェアで引っこ抜くが以前から興味があったネタだったのと、ちょうど自宅がメータ交換でスマートメータに変わったので、自分もmrubyを使って試してみました。
東京電力は三菱電機、GE富士電機メーター、東光東芝メーターシステムズの三社からメーターを調達しているようだが、こちらの自宅には東光東芝メーターシステムズのメーターが取り付けられている。
最終的にはFreeBSDで動いているデバイスで動かす事を考えているのですが、いきなり組み込みで動かすのは手間がかかるのでMacで試してみる事にしました。
今日の午前中に書いたメモはこれを試すための用意でした。
BP35A1は3.3V系なのでMacとの接続はストロベリーリナックスのFT2232Hなモジュールで接続しました。FTのモジュールはMPSSE/BitBangで利用する事もあるので、私はkextはシステムにはインストールせずに利用する時にkextloadコマンドでロードしてます。
とりあえずbuild_config.rbに以下を追加してmrubyをビルドしておきます。
conf.gem :github => 'monami-ya-mrb/mruby-serialport'
conf.gem :github => 'iij/mruby-pack'
mrubyのシリアルサポートのためのライブラリはいくつかあるようだが、違いは確認してません。
rukihenaさんのpythonのコードをmrubyにポートしてみました。
rbid = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
rbpwd = "XXXXXXXXXXXX"
ser = SerialPort.new("/dev/cu.usbserial-000012FDA", 115200, 8, 1, 0)
ser.flow_control=0
ser.puts "SKVER¥r¥n"
p ser.gets
p ser.gets
ser.puts "SKSETPWD C " + rbpwd + "¥r¥n"
p ser.gets
p ser.gets
ser.puts "SKSETRBID " + rbid + "¥r¥n"
p ser.gets
p ser.gets
scanRes = {}
scanDuration = 4;
while scanDuration < 7 do
ser.puts "SKSCAN 2 FFFFFFFF " + scanDuration.to_s + "¥r¥n"
while true do
line = ser.gets
if line != "" then
p line
if line[0, 8] == "EVENT 20" then
end
if line[0, 2] == " " then
cols = line[2..-3].split(":")
scanRes[cols[0]] = cols[1]
end
if line[0, 8] == "EVENT 22" then
if scanRes.length != 0 then
scanDuration = 7
else
scanDuration = scanDuration + 1
end
break
end
end
end
end
if scanRes.length != 0 then
ser.puts "SKSREG S2 " + scanRes["Channel"] + "¥r¥n"
p ser.gets
p ser.gets
ser.puts "SKSREG S3 " + scanRes["Pan ID"] + "¥r¥n"
p ser.gets
p ser.gets
ser.puts "SKLL64 " + scanRes["Addr"] + "¥r¥n"
p ser.gets
ipv6Addr = ser.gets
ipv6Addr.chop!
p ipv6Addr
ser.puts "SKJOIN " + ipv6Addr + "¥r¥n"
p ser.gets
p ser.gets
while true do
line = ser.gets
if line != "" then
p line
if line[0, 8] == "EVENT 25" then
p ser.gets
echonetLiteFrame = ""
echonetLiteFrame += "¥x10¥x81"
echonetLiteFrame += "¥x00¥x01"
echonetLiteFrame += "¥x05¥xFF¥x01"
echonetLiteFrame += "¥x02¥x88¥x01"
echonetLiteFrame += "¥x62"
echonetLiteFrame += "¥x01"
echonetLiteFrame += "¥xE7"
echonetLiteFrame += "¥x00"
command = "SKSENDTO 1 " + ipv6Addr + " 0E1A 1 "
command += sprintf("%04X", echonetLiteFrame.length)
command += " " + echonetLiteFrame
p command
ser.write command
p ser.gets
line = ser.gets
ev21 = line.split(" ")
p ser.gets
if ev21[3] == "00" then # 01 is no response
line = ser.gets
p line
if line[0, 6] == "ERXUDP" then
res = line[-20,20]
seoj = res[4,3].unpack("H*")
ESV = res[10].unpack("H*")
if seoj[0] == "028801" and ESV[0] == "72" then
EPC = res[12].unpack("H*")
if EPC[0] == "e7" then
hexPower = res[14,4].unpack("H*")
intPower = hexPower[0].to_i(16)
p "current max power:" + intPower.to_s + "[W]"
end
end
end
end
break
end
if line[0, 8] == "EVENT 24" then
break
end
end
end
end
% ./mruby smtest.rb
略
"current max power:430[W]"
バイナリーフォーマットのERXUDPを処理する部分にちょっとひっかかりましたが、iij/mruby-packを利用する事で解決できました。
当初ERXUDPのデータをchop!していたら、最終バイトがCRになったときに削られてしまい、おかしな数値になっていたので、chopしないようにしました。
ERXUDPをsplitていたところ、最終バイトが改行コードになってしまったときに取られてしまい変な値になっていたので、オフセットでバイナリを抜くようにしました。
ちゃんと仕様書読めってことかもしれませんが、シーケンスがテキストベースなのに、データにバイナリが入ってきた上に、改行が末尾にあるという仕様は、上のようにバグを作り込む要因になってしまうのではないでしょうか。。。追記:アスキーフォーマットにもできるみたいです。
またバグ作り込んでいたのですが、シリアルのライブラリのデフォルトがSOFTフォローでバイナリ部分からXON(0x11)が消えてしまって不正なデータになってしまっていました。フォローをNONE(0)にしました。。。
当初SerialPortのread_timeoutを設定していたのですが、canonicalなので意味が無いようです。実際に動かしているとたまにレスポンスが返ってこなくなり、止まってしまう事があるので、とりあえずkillして止めるようにしています。
余談ですがiijさんもスマートメーターに対応した機器を作ってますが、内部はNetBSDでmrubyとかで動いてるのかな?
半日くらいで試す事ができたのは、rukihenaさんの記事のおかげで、感謝してます。
スマートメーターから拾える値は、上の例のその時点での利用量(W)とメータの値(kWh)、および30分毎の使用量のようです。30分毎のデータは見た目には分かりやすいですが、現実的なアクションにつながりづらく、前者二つをいかにうまく伝えるかを考えるのが良い様な気がします。
#おまけ
BP35A1はコマンドでスリープモードに入り、ピンのレベル変化でウエイクアップできます。消費電力がかなり押さえられるようなので、FreeBSD/mipsにつないだBP35A1で試してみました。
起動後にU-Bootと干渉しないようにGPIO制御のバッファをかましていて、GPIOの制御線をそのまま変換基板のBP35A7AのWKUPに接続します。
スリープに入るには、SKDSLEEPコマンドを使います。
復帰するにはGPIOの11ピンで制御している場合はgpioctlで
# gpioctl 11 1
# gpioctl 11 0
# gpioctl 11 1
すると復帰して、TX,RXも有効になります。10mAくらいの節約なので、びびたるものですが。
後日追記:170日くらい動かしたところ、1日以上スキャンしても見つからない状態になってしまいました、BP35A1のつなぎなおしたら、復帰したので、ハードウエアリセットが必要だったようです。このためGPIOはウエイクアップではなくRESETに接続して見つからなくなった時はリセットをかけることにしました。
2019/8 FON2405が最初ネットワークが使えなくなり再起動したらその時は起動しましたが、じきに完全に起動しなくなり、壊れてしまったのでFON2412に代えました。今年の夏は暑い日が続いているので、熱による影響が考えられるのですが、これらのFONはSOCが基板裏に実装されているので、上下逆さまに置いたほうが空気の対流がスムーズな気がします。また底面にはスリットも入っているので空気が抜けやすいと考えられます。
FON2412は解析してuartfのTX(GPIO8)がR118でプルアップされていることがわかり、二つuartが使えるので良いです。
mruby 3.2にしたところシリアルポートのgetsが帰ってこなくなったので、getcでgetsの代わりの関数を作って対応しました。