mrubyでスマートメーター

[スマートメーターの情報を最安ハードウェアで引っこ抜く](http://qiita.com/rukihena/items/82266ed3a43e4b652adb)が以前から興味があったネタだったのと、ちょうど自宅がメータ交換でスマートメータに変わったので、自分もmrubyを使って試してみました。

東京電力は三菱電機、GE富士電機メーター、東光東芝メーターシステムズの三社からメーターを調達しているようだが、こちらの自宅には東光東芝メーターシステムズのメーターが取り付けられている。

最終的には[FreeBSDで動いているデバイス](http://qiita.com/yamori813/items/038027275d21e87388cd)で動かす事を考えているのですが、いきなり組み込みで動かすのは手間がかかるのでMacで試してみる事にしました。

今日の午前中に書いた[メモ](http://qiita.com/yamori813/items/8ac7f980082bf80fce45)はこれを試すための用意でした。

BP35A1は3.3V系なのでMacとの接続はストロベリーリナックスのFT2232Hなモジュールで接続しました。FTのモジュールはMPSSE/BitBangで利用する事もあるので、私はkextはシステムにはインストールせずに利用する時にkextloadコマンドでロードしてます。

![IMGP0194.JPG](https://qiita-image-store.s3.amazonaws.com/0/104066/973f9647-32cb-3c21-9600-963d99f6f3e4.jpeg)


とりあえずbuild_config.rbに以下を追加してmrubyをビルドしておきます。

```
  conf.gem :github => 'monami-ya-mrb/mruby-serialport'
  conf.gem :github => 'iij/mruby-pack'
```

mrubyのシリアルサポートのための[ライブラリ](http://mruby.org/libraries/)はいくつかあるようだが、違いは確認してません。

rukihenaさんのpythonのコードをmrubyにポートしてみました。

```ruby
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さんも[スマートメーターに対応した機器](http://qiita.com/sgrowd/items/d4aa644121b08ef11256)を作ってますが、内部はNetBSDでmrubyとかで動いてるのかな？

半日くらいで試す事ができたのは、rukihenaさんの記事のおかげで、感謝してます。

スマートメーターから拾える値は、上の例のその時点での利用量(W)とメータの値(kWh)、および30分毎の使用量のようです。30分毎のデータは見た目には分かりやすいですが、現実的なアクションにつながりづらく、前者二つをいかにうまく伝えるかを考えるのが良い様な気がします。

#おまけ

BP35A1はコマンドでスリープモードに入り、ピンのレベル変化でウエイクアップできます。消費電力がかなり押さえられるようなので、FreeBSD/mipsにつないだBP35A1で試してみました。

起動後にU-Bootと干渉しないように[GPIO制御のバッファ](http://qiita.com/yamori813/items/54d6b1a8b7d0e3be6c07)をかましていて、GPIOの制御線をそのまま変換基板のBP35A7AのWKUPに接続します。

スリープに入るには、SKDSLEEPコマンドを使います。

復帰するにはGPIOの11ピンで制御している場合はgpioctlで

```
# gpioctl 11 1
# gpioctl 11 0
# gpioctl 11 1
```

すると復帰して、TX,RXも有効になります。10mAくらいの節約なので、びびたるものですが。

<a data-flickr-embed="true"  href="https://www.flickr.com/photos/14000636@N03/29433240676/in/dateposted-public/" title="IMGP0009"><img src="https://c5.staticflickr.com/9/8062/29433240676_342470f031_z.jpg" width="480" height="640" alt="IMGP0009"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script>

後日追記：170日くらい動かしたところ、1日以上スキャンしても見つからない状態になってしまいました、BP35A1のつなぎなおしたら、復帰したので、ハードウエアリセットが必要だったようです。このためGPIOはウエイクアップではなくRESETに接続して見つからなくなった時はリセットをかけることにしました。

2019/8 FON2405が最初ネットワークが使えなくなり再起動したらその時は起動しましたが、じきに完全に起動しなくなり、壊れてしまったのでFON2412に代えました。今年の夏は暑い日が続いているので、熱による影響が考えられるのですが、これらのFONはSOCが基板裏に実装されているので、上下逆さまに置いたほうが空気の対流がスムーズな気がします。また底面にはスリットも入っているので空気が抜けやすいと考えられます。

FON2412は解析してuartfのTX(GPIO8)がR118でプルアップされていることがわかり、二つuartが使えるので良いです。

mruby 3.2にしたところシリアルポートのgetsが帰ってこなくなったので、getcでgetsの代わりの関数を作って対応しました。