こんにちは、ゆうかりです。
今回は、以下の記事で中途半端に使ってみたけど放置していたWi-SUN関連機材を使って。
消費電力計を作ってみた、というお話です。
Bルート契約周りとかは、以下記事をご確認くださいな。
obnizでスマートメーターにちょっかいを出してみる
https://elchika.com/article/142b9500-8c1c-4b7d-8d38-b515c752925b/
消費電力の可視化
まあ、プロダクトベースだとタブレットだの太陽電池コントローラ上に表示する系の「HEMS」機器や。
https://sumai.panasonic.jp/aiseg/hems/index.html
m5stack系オプションに、m5stickCな画面に表示するステキさんとかがあります。
https://booth.pm/ja/items/1650727
・・・まあこのヘン使うのが、気軽でいいですよ、と。
このお話は、そのへんぶっちぎって適当にWi-SUNモジュールをobniz案件だ!っつーて買った挙句に放置してしまって勿体ないので使いましょう、という、かなりこう意識が低いお話です。
買って放置してたWi-SUNモジュール、ROHMのBP35C0という、まあ比較的使い辛いやーつ。
ラズパイでとりあえず制御できるか
ラズパイと、秋月のFT232基板使って、適当に組みました。
通信はUSB変換してUSB経由にする方向で。
注意点としては、FT232はIOをスイッチで3.3Vにすることが出来ますが、3.3V電源は搭載していない為、別途用意する必要があること、くらいです。
適当に組んでる時は、ラズパイから3.3Vを貰ってましたとさ。
で、動作確認。
スクリプトは以下URLなどを参照に。
https://qiita.com/rukihena/items/82266ed3a43e4b652adb
https://github.com/katsumin/python-echonet-lite
なんだけど、、、最近のRaspberry Pi OSはpythonが3系しか搭載しておらず。
まあ結構大胆にスクリプト書き替えが必要でしたとさ、メンディー・・・。
基板作製
ま、動作確認はうまくいったので。
サクッと基板を作りましょう。
秋月のUSBシリアル変換、東芝の3.3V LDO、そして秋月C基板です。
こんなかんじで。
RESET端子、制御必要かと思いきや、単純にVCCに吊っちゃっていいみたいです。
CN No | 端子名 | 接続 |
---|---|---|
CN1 1 | GND | GND |
CN1 2 | ADC1 | 未接続 |
CN1 3 | ADC2 | 未接続 |
CN1 4 | VCC | 3.3V |
CN1 5 | VCC | 3.3V |
CN1 6 | GPIOA7 | 未接続 |
CN1 7 | MODE2 | GND |
CN1 8 | MODE0 | GND |
CN1 9 | GND | GND |
CN2 1 | GND | GND |
CN2 2 | RTS | 未接続 |
CN2 3 | CTS | 未接続 |
CN2 4 | RXD | FT232 TX |
CN2 5 | TXD | FT232 RX |
CN2 6 | SCL | 未接続 |
CN2 7 | RESET | 3.3V |
CN2 8 | SDA | 未接続 |
CN2 9 | GND | GND |
組み立て
スクリプト
以下2つを作製。
いずれも、systemdでの自動起動対応、自動リブート用にリセット処理を入れている感じです。
まず、消費電力をメーターから読み取って、「/ramdisk/watt.data」に書き出すスクリプト。
getwatt.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import serial
import time
import os
# Bルート認証ID
rbid = 'BルートのID'
# Bルート認証パスワード
rbpwd = 'Bルートのパスワード'
# シリアルポートデバイス名
serialPortDev = '/dev/ttyUSB0'
# リザルトファイル
outputfilename = '/ramdisk/watt.data'
def debugwrite(data):
# print (data)
pass
def sendcommand(serialobject,data):
data = data + '\r\n'
serialobject.write(data.encode())
readdata = serialobject.readline()
readdata = serialobject.readline()
debugwrite (readdata.decode())
def sendcommandraw(serialobject,data):
data = data + '\r\n'
serialobject.write(data.encode())
def sendcommandbinary(serialobject,data):
serialobject.write(data)
# serialobject.flush()
def receivecommandraw(serialobject):
readdata = serialobject.readline()
return (readdata)
# main loop
while True:
# シリアルポート初期化
if not (os.path.exists(serialPortDev)):
time.sleep(10)
continue
debugwrite('[init]')
ser = serial.Serial(serialPortDev, 115200)
# とりあえずバージョンを取得してみる(やらなくてもおk)
debugwrite('[version]')
sendcommand(ser,'SKVER')
# Bルート認証パスワード設定
debugwrite('[password]')
sendcommand(ser,'SKSETPWD C ' + rbpwd)
# Bルート認証ID設定
debugwrite('[id]')
sendcommand(ser,'SKSETRBID ' + rbid)
scanDuration = 4; # スキャン時間。サンプルでは6なんだけど、4でも行けるので。(ダメなら増やして再試行)
scanRes = {} # スキャン結果の入れ物
debugwrite('[scan]')
# スキャンのリトライループ(何か見つかるまで)
while not 'Channel' in scanRes:
# アクティブスキャン(IE あり)を行う
# 時間かかります。10秒ぐらい?
sendcommandraw(ser,'SKSCAN 2 FFFFFFFF ' + str(scanDuration) + ' 0')
# スキャン1回について、スキャン終了までのループ
scanEnd = False
while not scanEnd :
line = receivecommandraw(ser)
debugwrite (line)
if line.startswith(b'EVENT 22') :
# スキャン終わったよ(見つかったかどうかは関係なく)
scanEnd = True
elif line.startswith(b' ') :
# スキャンして見つかったらスペース2個あけてデータがやってくる
# 例
# Channel:39
# Channel Page:09
# Pan ID:FFFF
# Addr:FFFFFFFFFFFFFFFF
# LQI:A7
# PairID:FFFFFFFF
linedecode =line.decode()
cols = linedecode.strip().split(':')
scanRes[cols[0]] = cols[1]
scanDuration+=1
if 10 < scanDuration and not 'Channel' in scanRes:
# 引数としては14まで指定できるが、7で失敗したらそれ以上は無駄っぽい
continue
if scanEnd == False:
ser.close()
time.sleep(10)
continue
# スキャン結果からChannelを設定。
debugwrite('[set channnel]')
sendcommand(ser,'SKSREG S2 ' + scanRes['Channel'])
# スキャン結果からPan IDを設定
debugwrite('[set panid]')
sendcommand(ser,'SKSREG S3 ' + scanRes['Pan ID'])
# MACアドレス(64bit)をIPV6リンクローカルアドレスに変換。
# (BP35A1の機能を使って変換しているけど、単に文字列変換すればいいのではという話も??)
debugwrite('[set localaddress]')
sendcommandraw(ser,'SKLL64 ' + scanRes['Addr'])
readdata = receivecommandraw(ser)
ipv6Addr = receivecommandraw(ser).decode().strip()
# print(ipv6Addr)
# PANA 接続シーケンスを開始します。
debugwrite('[join]')
sendcommand(ser,'SKJOIN ' + ipv6Addr)
# PANA 接続完了待ち(10行ぐらいなんか返してくる)
debugwrite('[wait panaconnect]')
bConnected = False
bCount = 0
while not bConnected :
line = receivecommandraw(ser)
debugwrite(line)
if line.startswith(b'EVENT 24') :
continue
elif line.startswith(b'EVENT 25') :
# 接続完了!
bConnected = True
bCount = bCount + 1
if bCount > 30:
continue
if bConnected == False:
ser.close()
time.sleep(10)
continue
# これ以降、シリアル通信のタイムアウトを設定
ser.timeout = 2
# スマートメーターがインスタンスリスト通知を投げてくる
# (ECHONET-Lite_Ver.1.12_02.pdf p.4-16)
readdata = receivecommandraw(ser) # 無視
readdata = receivecommandraw(ser) # 無視
time.sleep(2)
debugwrite('[start]')
protime = time.time()
while True:
ntime = time.time()
ptime = ntime - protime
if ptime > 120:
break
# ECHONET Lite フレーム作成
# 参考資料
# ・ECHONET-Lite_Ver.1.12_02.pdf (以下 EL)
# ・Appendix_H.pdf (以下 AppH)
echonetLiteFrame = b''
echonetLiteFrame += b'\x10\x81' # EHD (参考:EL p.3-2)
echonetLiteFrame += b'\x00\x01' # TID (参考:EL p.3-3)
# ここから EDATA
echonetLiteFrame += b'\x05\xFF\x01' # SEOJ (参考:EL p.3-3 AppH p.3-408~)
echonetLiteFrame += b'\x02\x88\x01' # DEOJ (参考:EL p.3-3 AppH p.3-274~)
echonetLiteFrame += b'\x62' # ESV(62:プロパティ値読み出し要求) (参考:EL p.3-5)
echonetLiteFrame += b'\x01' # OPC(1個)(参考:EL p.3-7)
echonetLiteFrame += b'\xE7' # EPC(参考:EL p.3-7 AppH p.3-275)
echonetLiteFrame += b'\x00' # PDC(参考:EL p.3-9)
# コマンド送信
command = 'SKSENDTO 1 {0} 0E1A 1 0 {1:04X} '.format(ipv6Addr, len(echonetLiteFrame))
command = command.encode() + echonetLiteFrame + b'\r\n'
sendcommandbinary(ser,command)
for loopi in range(10):
line = receivecommandraw(ser)
# 受信データはたまに違うデータが来たり、
# 取りこぼしたりして変なデータを拾うことがあるので
# チェックを厳しめにしてます。
if line.startswith(b'ERXUDP') :
cols = line.strip().split(b' ')
res = cols[9] # UDP受信データ部分
seoj = res[4:7]
ESV = res[10:11]
if seoj == b'\x02\x88\x01' and ESV == b'\x72' :
# スマートメーター(028801)から来た応答(72)なら
EPC = res[12:13]
if EPC == b'\xe7' :
# 内容が瞬時電力計測値(E7)だったら
hexPower = res[14:18] # 最後の4バイト(16進数で8文字)が瞬時電力計測値
intPower = int.from_bytes(hexPower, 'big')
strPower = str(intPower)
if len(strPower) > 0:
f = open(outputfilename,'w')
f.write (str(intPower))
f.close()
protime = time.time()
time.sleep(5)
break
ser.close()
rpi-rgb-led-matrixライブラリを使って、「/ramdisk/watt.data」の内容を表示するスクリプト。
ledoutput.py
from PIL import Image, ImageDraw, ImageOps, ImageFont, ImageFilter, ImageChops, ImageColor
from rgbmatrix import RGBMatrix, RGBMatrixOptions
import time
outputfilename = '/ramdisk/watt.data'
class ledworker(object):
def ledinit(self):
self.options = RGBMatrixOptions()
self.options.hardware_mapping = "regular"
self.options.led_rgb_sequence = "RGB"
self.options.rows = 32
self.options.chain_length = 2
self.options.parallel = 1
self.options.pwm_bits = 11
self.options.brightness = 70
self.options.pwm_lsb_nanoseconds = 130
self.options.gpio_slowdown = 3
self.matrix = RGBMatrix(options = self.options)
self.fontdata = ImageFont.truetype('/data/Envy Code R Bold.ttf', 32)
self.fontdatasmall = ImageFont.truetype('/data/Envy Code R Bold.ttf', 12)
def ledoutput(self,data):
image = Image.new('RGB', (64, 32), (0, 0, 0))
draw = ImageDraw.Draw(image)
outputdata = float(data) / 1000
outputdata = '{:.04f}'.format(outputdata)
draw.text((8,-5), '.', (192, 192, 192),font=self.fontdata)
draw.text((52,20), 'kw', (192, 192, 192),font=self.fontdatasmall)
draw.text((0,-5), outputdata[0], (255, 255, 255),font=self.fontdata)
draw.text((17,-5), outputdata[2], (255, 255, 255),font=self.fontdata)
draw.text((33,-5), outputdata[3], (255, 255, 255),font=self.fontdata)
draw.text((49,-5), outputdata[4], (255, 255, 255),font=self.fontdata)
self.matrix.SetImage(image)
led = ledworker()
led.ledinit()
while True:
try:
f = open(outputfilename, 'r')
data = f.read()
f.close()
led.ledoutput(data)
time.sleep(1)
except:
pass
フォントは、「Envy Code」を使っています。
https://damieng.com/blog/2008/05/26/envy-code-r-preview-7-coding-font-released/
アンテナの選定
さて、今回使用しているWi-SUNモジュールには。
アンテナが付属しておりません。
で、Wi-SUNの物理層には、「LQI(Link Quality Indication)」という、リンク品質を確認する項目が定義されており、まあそれを使ってアンテナの簡易的な評価をすることができます。
手持ちのアンテナや、そのへんに売っているアンテナを使って、比較してみました。
比較ラインアップは、以下の通り。
適当な2.4GHz用アンテナ
NUC的なパソコンについてきたやーつ。
多分WiFiだのBTだの用。
適当なリグ用の標準アンテナ
900MHz帯受信にも対応しているホイップアンテナ
ダイヤモンドアンテナの、SRHF10、アキバの富士無線さんで購入。
秋月のラバーダックアンテナ
アンテナは、秋月最強でした
ほぼ同一条件で、アンテナだけ変更して。
ま、サクッと測ってみたら。
アンテナ | LQI |
---|---|
2.4GHzアンテナ | 28 |
VX-8アンテナ | 9C |
SRHF10 | 60 |
秋月アンテナ | B1 |
うむ、高くないし、秋月アンテナ使いましょう!。
*アンテナ感度とか確認してないので、よいこはまねしないでくださいね・・・。
というわけで、出来ました!
いやあ、消費電力がバーンと出てるので、エアコンつけっぱなしとか一発で分かっていい感じです!。
ま、消費電力自体はファイルに書き出しているので、FirebaseだのZabbixだの連携も組もうと思えば組めますが。
とりあえず「その場でパッと分かって節電」的な用途は、満たせた感じ!。
分かりやすいって大事よね!。
ま、つーわけで。
なかなかに満足のいけるものができましたとさ!。
以上です。
投稿者の人気記事
-
eucaly
さんが
2022/01/23
に
編集
をしました。
(メッセージ: 初版)
-
eucaly
さんが
2022/01/23
に
編集
をしました。
Opening
ozwk
2022/01/24 Opening
eucaly
2022/01/25
ログインしてコメントを投稿する技適はモジュールとアンテナの組み合わせで取得するものだった気がするのですが、本例は大丈夫でしょうか?
BP35C0のハードウェア仕様書を見ると、
とありますし、
BP35C0 日本電波法認証済外付けアンテナリスト
という資料も存在しています。
技適取得条件につきましては、確認しておりません。
アンテナ評価につきましては、当該資料を確認の上、
・無指向性
・利得3dbi以下の構造
のもので、アンテナマッチングを無視して行っているので、利得要件には引っかかってはおりません。