6月 082023
 

前回はBルートで使用されるEDATA電文について説明しました。今回はWSUHAのコマンドを使用してスマー
トメータと通信を行い、Windows11 デスクトップの右隅に瞬間消費電力値と時刻を表示するアプリケーションプログラムを紹介します。時刻表示は1秒ごとに更新され、’00秒’毎にスマートメータから瞬間消費電力値を取得して表示を更新します。また「更新」のボタンをClickするとその時点での瞬間消費電力値をスマートメータから取得して表示を行います。Programは土壌センサの応用例と同様にWindows11上のPython3で作成しました。
ウイジェットなどのGUIはPythonのライブラリtkinterを使用しています。WSUHAはCOMxポートとしてpyserial ライブラリを使用します。Windows11へのPythonのインストールや動作環境の設定、pyserialのinstall、tkinter の使いかたなどはnet上で公開されている記事を参照して下さい。

スマートメータ Bルートでの通信実行部

スマートメータとBルートで通信するためのスクリプト例(TeraTerm用)はROHMの公式サイトからdownload
することができます。download後解凍すると4種類のTeraterm用スクリプトファイル(*.ttl)が得られますが、B
ルートを通してスマートメータにアクセスする場合(今回)は「1_HGW(DSE).ttl」を参考にします。ただし、Bル
ート側だけでなくECHONET Lite機器とのHAN側との設定や通信例も含んでいますので今回はBルートを対象と
した部分と共通部分を参考にするだけとします。
今回のprogramで使用するWSUHAのコマンド(SK Command)は下表のとおりです。

画像をクリックすると拡大表示

WSUHAとのインターフェイス部はライブラリ pyserialを使用します。programの先頭部でimport serialという
宣言が必要ですので事前にinstallしておいて下さい。

>python -m -pip install pyserial

今回のSample programのWSUHA、スマートメータとの通信部を示します。

#  Set up WiSun and connection to B-Route

COM="COM3"       # RS-WSUHA-P on Windows11 / Check COM port number by Device Manager
bitRate=115200
CRLF = bytes([ 13,10 ])

rbpwd =  "02xxxxUAxxxx"                        # Authentication pass word 送配電会社から通知されたPWに変更して下さい
rbid =  "0000009A05xxxx88400050xxxxxxD4C0"     # Authentication ID 送配電会社から通知された認識用IDに変更して下さい

ser = serial.Serial(COM, bitRate, timeout=10 )     # init Serial Port

print("\r\nWSUHA & SmartMeter communication test 6R0\r\n ")

# Reset WSUHA command buffer
ser.write(b'SKRESET\r\n')
skcmd = b"SKRESET"
rtn_cd = receive_echoback( skcmd )        # get ECHOBACK
rtn_cd = receive_OK()                     # get 'OK'

#  WSUHA F.W version Check
#ser.write(b'SKVER\r\n')
# ecbk = ser.readline()  # echo back
#print( "Echo back:", ser.readline(), "\r\n")
#print("BP3501 Version:", ser.readline(), "\r\n")

# Set B-route Authentication Password 
ser.write(b'SKSETPWD C ')
ser.write( rbpwd.encode('utf-8'))
ser.write( CRLF )
skcmd = b"SKSETPWD"
rtn_cd = receive_echoback( skcmd )        # get ECHOBACK
rtn_cd = receive_OK()                     # get 'OK'


# Set B-route Authentication ID
ser.write(b'SKSETRBID ')
ser.write(rbid.encode('utf-8') )
ser.write( CRLF )
skcmd = b"SKSETRBID"
rtn_cd = receive_echoback( skcmd )        # get ECHOBACK
rtn_cd = receive_OK()                     # get 'OK'

#  B-Route scan to find SmartMeter
scanduration = 6       
scan_data = {}      # area for scaned results

print("\r\nScan start to detect SmartMeter\r\n")
ser.write(b"SKSCAN 2 FFFFFFFF 6 0 \r\n") 
skcmd = b"SKSCAN"
rtn_cd = receive_echoback( skcmd )        # get ECHOBACK
rtn_cd = receive_OK()                     # get 'OK'
# Retry SCAN to get some responce from SmartMeter
scan_loop = True
while scan_loop :
    rcvmsg = receive_msg()
    print("Scan Result:", rcvmsg, "\r\n", end="")

    if rcvmsg.startswith(b"EVENT 22") :
    # SCAN ends : receive EVENT 22
        scan_loop = False
    elif rcvmsg.startswith(b"  ") :
            # Receive Data after doubel space
            #  "  "
            #  Channel:39
            #  Channel Page:09
            #  Pan ID:FFFF
            #  Addr:FFFFFFFFFFFFFFFF
            #  LQI:A7
            #  PairID:FFFFFFFF
        cols = rcvmsg.strip().split(b':')
        scan_data[cols[0]] = cols[1]

# pull out Channel and set it to S2 reg.
ser.write(b"SKSREG S2 " + scan_data[b"Channel"] + b"\r\n")
skcmd = b"SKSREG"
rtn_cd = receive_echoback( skcmd )        # get ECHOBACK
rtn_cd = receive_OK()                     # get 'OK'

# pull out Pan ID and set it to S3 reg.
ser.write(b"SKSREG S3 " + scan_data[b"Pan ID"] + b"\r\n")
skcmd = b"SKSREG"
rtn_cd = receive_echoback( skcmd )        # get ECHOBACK
rtn_cd = receive_OK()                     # get 'OK'

# Convert MAC Address(64bit) to IPV6 address
ser.write(b"SKLL64 " + scan_data[b"Addr"] + b"\r\n")
skcmd = b"SKLL64"
rtn_cd = receive_echoback( skcmd )        # get ECHOBACK
ipv6Addr = ser.readline().strip()
print("IPv6 Address:", ipv6Addr, "\r\n")

# start to set up PANA Connection sequence
ser.write(b"SKJOIN " + ipv6Addr + b"\r\n");
skcmd = b"SKJOIN"
rtn_cd = receive_echoback( skcmd )        # get ECHOBACK
rtn_cd = receive_OK()                     # get 'OK'

# Wait PANA Connection completes until receive EVENT25.
bConnected = False
while not bConnected :
    rcvmsg = receive_msg()
    print(rcvmsg, "\r\n")
    if rcvmsg.startswith(b"EVENT 24") :
        print("PANA Connection failed.", "\r\n")
        sys.exit()  # Quit to Windows11 
    elif rcvmsg.startswith(b"EVENT 25") :
        print("PANA Connection completed.", "\r\n" )
        bConnected = True

最初に事前にDevice Managerで見つけておいたCOMポート番号(COM3など)、WSUHAのbitrate(default値は
115200)、送配電会社から通知されたスマートメータのPass Wordと識別IDなどを変数にセットしておきます。
上記のrbpwd、rbidに記述している値はDummyですので実際に受け取った値を記述して下さい

その後serialポートの宣言と初期化を行った後、順番にSKコマンドを発行してゆきます。Programをスッキリさせるためには発行したコマンドに対するECHOBACKはない方がよいのですが今回はdebugのために
ECHOBACKをONにしてあります。ECHOBACK のON/OFFはSFEレジスタの設定でコントロールすることができます。’1’をsetするとON(default設定)、’0’をsetするとOFFになりますが今回は操作せずdefaultのまま使用します。

シリアルポート初期化に続いてWSUHAにコマンドを送ってゆきます。送信するコマンドや順番はSample
Script(1_HGW(DSE).ttl)を参考にしています。使用するコマンドは表-1を参照して下さい。表中の各コマンドの詳細はROHM公式ページからdownloadした「BP35C0/BP35C2コマンドマニュアル Version 1.1.0」を参照して下さい。

シリアルポート初期化後(初期化失敗時の処理は行っていません)、最初にWSUHA内部の処理や通信をResetす
るためのSKRESETコマンドをser.write(b’SRESET\r\n’)で送信します。8bitデータで通信しますのでb’ ‘が必要です。また指定されたコマンド以外は<CRLF>がターミネータとして必要ですのでb’\r\n’をコマンドの後ろに追加しています。コマンド送信後は正しくEchobackされるかどうかのcheckとコマンド実行正常終了を示す”OKをCheckします。WSUHAとの間で送受信のシーケンスが乱されることがありますのでこれらの専用受信関数を作成しました。WSUHAが無応答の場合などはError Codeを返すようにしていますが、今回のProgramでは実験のためエラー処理は行っていません。

SKRESETが正しく実行されたら次は送配電会社から通知されたスマートメータ(電力計)の識別IDとパスワードをSKSETPWD、SKSETRBIDコマンドで設定します。それぞれ通知された文字列をrbpwd、rbidという変数に代入していますのでコマンドの電文にはUTF-8で8bitバイナリ列に変換してser.write()で送信します。SKSETPWDコマンドではrbpwdの文字数(バイト数)をUINT8で指定しなければならないので’C’(12文字)をコマンドとrbpwdの間に入れる必要があります。スマートメータのIDとPWの設定の正常終了を確認後はSKSCANコマンドでスマートメータを探します。

SKSCANコマンドにはMODE、CHANNEL_MASK、DURATION、SIDEという4種類の引数が必要ですがSIDE(Bルートは0)以外は内容がよくわかりませんのでコマンドマニュアルとSample ScriptにならってMODE=2(アクティブスキャン IEあり)、CHANNEL_MASKはFFFFFFFF(すべてのチャンネルをスキャン)、DURATION=6とします。したがってb”SKSCAN 2 FFFFFFFF 6 0 \r\n”を発行します。EchobackとOKが返ってきた後、10秒ほど間をおいて
(ser.readline()のtimeoutを10秒に設定していますのでもし間に合わない場合は20秒くらいに設定しておいて下さい)EVENT 20、EPANDESCが返ってきます。programはEVENT 22が返ってくるまでwhileループで受信を続けます。各イベントはコマンドマニュアルの「第4章 4.イベント」を参照して下さい。下記のように10行の電文が送られてきますのでwhileループで受信し、発見したPANに関する情報(ChannelからPair IDまで)をそれぞれ変数に入れておく必要があります。

EVENT 20 <WSUHAのAddress> 0   #BルートでBeaconを受信したことの通知。
EPANDESC
  Cannel:2F
  Cannel Page:09
  Pan ID:88xx                   # xxはdummy 伏字
  Addr:8CDFxxxxFEC12Cxx         # xxはdummy 伏字
  LQI:BC
  Side:0
  PairID:01ADxxxx               # xxはdummy 伏字
EVENT 22 <WSUHAのAddress> 0    #アクティブスキャン完了通知。

EVENT 22まで受信を終えたのち、SKSREGコマンドでS2レジスタにCannel(EPANDESCで受信した2F)を、S3レジスタにPan ID(同じくEPANDESCで受信した88xx)をセットします。続いてEPANDESCで受信したスマートメータのアドレスをSKLL64コマンドを使用してIPv6アドレスに変換し、そのIPv6アドレスのスマートメータに対してSKJOINコマンドでPANA接続シーケンスを開始します。SKJOINコマンドの送信後、EchobackとOKが返ってきます。続いてEVENTが返ってきます。”EVENT 21 IPv6アドレス” や “EVENT 02 IPv6アドレス”が何回か返ってきますが “EVENT 25 IPv6アドレス”を受信するまではwhileで受信を繰り返してください。EVENT 25は接続完了通知ですのでこの通知を受信するまではwhileループにとどまっておいて下さい。 EVENT 25を受信するまでは約30~40秒要します。もし”EVENT 24 IPv6アドレス”を受信した場合、SKSETPWDコマンドで設定したスマートメータのPWかSKSETBIDコマンドで設定したスマートメータの識別IDが正しくないということを示しますので、Program の実行を中止しPWやIDの確認と修正を行ってください。

WSUHAからの電文の受信関数について

PANA結合後のスマートメータからは30分ごとに日付と時間およびその時点での積算電力値を含んだEDATAが
ERXUDPイベントとして送られてくるだけではなく、同じ頻度でEDATAのヘッダ部が”0000″のECHONET Lite様
式ではないEDATAを含むERXUDPイベントも送られてきます。そのためコマンドに対するECHOBACKや応答(OK)
のシーケンスが狂わされてしまうだけでなくSKJOINコマンドの応答ループにも割り込んできます。結果的に正しく電力値などをとり出すことができなくなってしまいます。したがって受信シーケンスを維持するために余計な受信電文を捨てて期待する電文を待つという関数を作成しておきます。

#   Wait SKCOMMAND Echoback from WSUHA
def receive_echoback( skcmd ):
    lpcont = 1
    while lpcont:
        rcv_echobk = ser.readline()
        if rcv_echobk == b"":               # Time Out, WSUHA did not send back SKCOMMAND
           lpcont = 2
           return lpcont
        if rcv_echobk.startswith( skcmd ) :
            print("Echoback : ", rcv_echobk )
            lpcont = 0
        else:
            lpcont = 1
    return lpcont

#  Wait 'OK' from WSUHA
def receive_OK():
    lpcont = 1
    while lpcont:
        rcvmsg = ser.readline()
        if rcvmsg == b'':
            lpcont = 2                          # Time out Error
            return lpcont 
        if rcvmsg.startswith(b"OK") :
            print("Ack : ", rcvmsg, "\r\n"  )
            lpcont = 0
        else:
            lpcont = 1
    return rcvmsg

今回のProgram例では受信Timeout時のError対応などは含んでおりません。EchobackやOK以外の必要な電文
の受信は下記の関数で行います。ERXUDPを受信した場合はEDATA部に含まれるTIDをSKSENDTOコマンドで送
信したTIDと一致しているかどうか照合し、一致する場合のみ(TID=’5253′)電力値などを取り出す処理を行います。ERXUDP以外の電文はそのまま素通りさせ、EVENT電文などの解析部へと引き渡しています。

def receive_msg():
    lpcont = 1
    while lpcont:
        rcvmsg = ser.readline()
        if rcvmsg.startswith(b"ERXUDP") :
            rcv_txt = rcvmsg.strip().split(b' ')
            rcv_edata = rcv_txt[9]               # UDP  EDATA 
            print("EDATA;", rcv_edata, "\r\n"  )
       	    rcv_tid = rcv_edata[4:4+4];
            print("Receiveed TID:", rcv_tid )
            if rcv_tid == b'5253' :              # check my TID ?
                lpcont = 0
            else:
                lpcpnt =1
        else:
            lpcont = 0
    return rcvmsg

SKSENDTO コマンドでスマートメータと通信する

SKJOINコマンドに対してEVENT 25(接続完了)が返ってきた後はスマートメータとの通信が可能になります。
SKSENDTOコマンドに続けてIPv6アドレスやBルートの指定などの引数に続けてECHONET Liteフォーマットの
EDATA(バイナリ)をWSUHAに送り込むとEDATA部がスマートメータに送信されます。EDATA部の構成は前回で
説明したようにECHONET Liteの仕様書を参考に決めてゆきます。今回のProgramでは瞬間消費電力値(W単位)をリクエストするためにPropertyレジスタとして”E7”を指定しました。”E7″の読み出し要求ですのでE7にSetするデータはありませんのでE7に続くデータ長(バイト数)を指定するエリア(バイト)は”00″とします。今回の
Programで使用しているSKSENDTOコマンドのEDATAは下記を参照して下さい。

# Econet Lite DATA frame
ehd1 = 0x10       # Econet Lite
ehd2 = 0x81       # EDATA format 1
tidh = 0x52       # Transaction ID H "R"
tidl = 0x53       # Transaction ID L "S"
Seoj_x1 = 0x05    # Source EOJ Class Group Code   管理操作機器グループ
Seoj_x2 = 0xFF    # Source EOJ Class Code      コントローラクラス
Seoj_x3 = 0x01    # Source EOJ Instance Code
Deoj_x1 = 0x02    # Destination EOJ Class Group Code 住宅関連機器グループ
Deoj_x2 = 0x88    # Destination EOJ Class Code    低圧スマート電力量メータクラス
Deoj_x3 = 0x01    # Destination EOJ Instance Code
esv_req = 0x62    # Econet Lite Service Code
#                 # Request to read out property value
#                 # and send back with esv(0x72)
opc_req = 0x01    # Number of property counter be red out.
epc_req = 0xE7    # Econet Property Counter name.
pdc_req = 0x00    # Property Data Byte count.

req_cmd = bytes([ehd1,ehd2,tidh,tidl,Seoj_x1,Seoj_x2,Seoj_x3,Deoj_x1,Deoj_x2,Deoj_x3,esv_req,opc_req,epc_req,pdc_req])

画像をクリックすると拡大表示

Pythonの特徴を活かしたもっとエレガントな方法があると思いますが強引にreq_cmdというByte列をbytes()で作成しました。SKSENDTOコマンドの例は下記を参照して下さい。GUIのウイジェットのボタンがClickされた時、また一定の間隔(1分、5分などの)で瞬間消費電力値を要求するためにSKSENDTOコマンドを発行し、スマートメータからの応答をERXUDPパケットで受信する関数get_edata()を作成しました。SKSENDTOコマンドのEDATA以外の各引数の値はSample Scriptを参考にしています。EDATA部のバイト数は14バイトですのでEDATA部の直前の引数は”000E “とします。HANDLEやPORTはSample Scriptにならって”1″、”0E1A”としています。

def get_edata( ipv6Addr, req_cmd ):

    # Send Data Request Command
    print("Send Request Command ")
    ser.write(b"SKSENDTO 1 " + ipv6Addr + b" 0E1A 1 0 000E ")
    ser.write( req_cmd )

    skcmd = b"SKSENDTO"
    rtn_cd = receive_echoback( skcmd )        # get ECHOBACK
    rtn_cd = receive_OK()                     # get 'OK'

    #  Get UDP packet includs EDATA
    rcvmsg = receive_msg()         # ERXUDP
    print( rcvmsg,"\r\n")
    intpower_str = "----W"         # error code

    if rcvmsg.startswith(b"ERXUDP") :
        print(  "ERXUDP received ", "\r\n")
        cols = rcvmsg.strip().split(b' ')
        res = cols[9]   # UDP  EDATA 
        print("EDATA;", res )
       	#tid = res[4:4+4];
        seoj = res[8:8+6]
        print("SEOJ:", seoj )
       	#deoj = res[14,14+6]
        ESV = res[20:20+2] 
        print("ESV:", ESV )
       	#OPC = res[22,22+2]
        if seoj == b'028801' and ESV == b'72':
        # Check ESV(Responce:72) and Source EOJ, is SEOJ SmartMeter(028801) ?
            EPC = res[24:24+2]
            print("EPC:", EPC, "\r\n" )
            # 瞬時電力計測値(E7)
            if EPC == b'E7':
                hexPower = res[28:28+8] 
                print( "Value:", hexPower, "\r\n") 
                intPower = int( hexPower, 16 )
                intpower_str=str(intPower)+"W"
                # return intpower_str
    return intpower_str


def value_update():
    text.set("更新中")
    # time.sleep(5)
    # Display New value
    global label
    global ipv6Addr
    global req_cmd
    global prv_pwrstr

    powstr_1= get_edata( ipv6Addr, req_cmd )
    prv_pwrstr = powstr_1

    now = time.strftime("%H:%M:%S")
    label.config( text=powstr_1 + " at " + now, font=("Times", '30'))
    # label.pack()
    #time.sleep( 5 )
    text.set("更新")
    return


def tick():
    global label
    global ipv6Addr
    global req_cmd
    global prv_pwrstr

    now = time.strftime("%H:%M:%S")
    now_sec = now[6:6+2]

    if now_sec == "00":
        powstr_1= get_edata( ipv6Addr, req_cmd )
        prv_pwrstr = powstr_1
    else:
        powstr_1 = prv_pwrstr

    now = time.strftime("%H:%M:%S")
    label.config(
        text=powstr_1 + " at " + now , font=("Times",'30'))
    label.after(1000, tick)
    return now

スマートメータからのEDATAはあらかじめ「WOPT 1」でWSUHAを設定していくことでEDATA部が16進文字列に変換されて送られてきます。(注 SKSENDTOコマンドに対するEchobackでは「WOPT 1」にかかわらずEDATA部はCutされて返ってきません) この関数の戻り値として瞬時電力計測値が10進文字列で返されます。
Programのそのほかの部分はPythonでGUIを扱うためのライブラリtkinterによるものです。tkinterの使用方法は本稿では説明いたしませんので「tkinterの使いかた」専門のBlogを参照して下さい。今回の瞬時電力計測値を読み出すProgramの全体のListはこちら(RS-WSUHAのwebページ)からdownloadすることができます。

>>サンプルスクリプトの全体はこちら⇒[別ウィンドウでサンプルスクリプト全体を表示]

スマートメータからは瞬時電力計測値だけではなく30分ごとの積算電力量を1日分まとめて取得することもできますので毎日の積算電力量を1か月分をまとめてExcelの表に集計するProgramもPythonで作成することができますので挑戦してみて下さい


この記事で紹介した製品

RS-WSUHA-P製品画像
RS-WSUHA-P - Wi-SUN Bルート・HAN対応USBアダプター

 返信する

以下のHTML タグと属性が利用できます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(必須)

(必須)

*