スマートメーターから瞬時電力を取得する その3

その1,その2では,ラズパイ + Wi-SUNモジュールを使い,スマートメータから取得した瞬時電力をWebブラウザに表示しましたが,さらに,瞬時電力のログ表示機能を実装しました。

1. スクリーンショット

まずは,瞬時電力の表示画面。
丸いメーターは,HighchartsAngular Gaugeを使っています。

img_5488

 

こちらは,ログ表示画面。10日分のログを表示します。
グラフはHighstockを使っています。

img_5489

 

iPhoneのSafariでは,タップやピンチ操作によりグラフをグリグリできたのに….。
iOS10になってから,SafariのせいなのかjQueryのせいなのか,グラフの再描画がうまくいかず,快適に操作できなくなってしまいました。残念。

 

2. インストール

  • git: これが無いと始まらない。なければラズパイのターミナルでsudo apt-get install gitを実行します。
  • node.js: 私がインストールしたのはv4.5.0ですが,本稿執筆時のLTS版はv4.6.1です。これでも動くでしょう。インストール方法はこちらに書きました。

/home/pi/hoge/に、github上のリポジトリのクローンを作成します。

 

2017/10/26追記

バージョンアップしました。0.6aをチェックアウトします。

 

3. セットアップ

ユーザー設定ファイルuser_conf.pyに,スマメのIDとパスワードを設定します。また,電力の取得間隔[秒]を設定します。なお,0に設定すれば最大頻度で電力を取得しますが,どんなに頑張っても通信に3秒程度かかり,それ以上速くなりません。

各無線チャンネルをスキャンして,スマメを探す作業をアクティブスキャンといいます。チャンネル当たりの探索時間を決定するパラメータがSEM_DURATIONで,通常は6でOKです。私の環境では,デフォルトの6でスマメを探すのに数十秒かかります。どうしてもスマメを見つけられないときは,この数字を+1すると良いでしょう。なお,数字が1増える毎に探索時間が2倍になりますので,あまり大きな数字を設定するといつまでたってもスキャンが終わらなくなります。

 

次にnode.jsが使うモジュールをインストールします。

 

Webサーバのポート番号はデフォルトで3610です。これを変更したい場合は,/home/pi/hoge/Wi-SUN_EnergyMeter/sem_app/bin/wwwを編集します。

 

4. 起動

まず,ターミナルで次のコマンド実行します。

 

[sock]: server boundの表示が現れたら,別のターミナルで次のコマンドを続行します。

  • ログファイルセットアップ
  • Wi-SUNリセット
  • アクティブスキャン(もし,10回失敗したら本プログラムを終了します)
  • PANA認証(もし,10回失敗したら本プログラムを終了します)
  • スマメから色々情報を取得(利用していませんけど)
  • 瞬時電力取得を繰り返す

といった流れです。

 

5. Webブラウザで確認

ラズパイにWebブラウザでアクセスしてみます。私の環境ではhttp://raspi0.local:3610です。

スクリーンショットで示したような画面が表示されれば成功です。

 

6. デーモン化と自動起動

ターミナルからいちいち起動するのは面倒ですので,デーモン化して自動起動するように設定します。

supervisorを使うのが手っ取り早いのでその設定を行います。

まず,supervisorをインストール。

 

sem_app/bin/wwwを自動起動する設定ファイルを編集します。

 

内容は次のとおりです。

“[program:sem_app]”は,supervisorが識別するための名称を指定するものです。「sem_app」と命名しました。

「autostart=true」を設定しているので,ラズパイの電源投入やリブート時に自動的に起動されます。

 

次に,sem_com.pyを自動起動する設定ファイルを編集します。

 

内容は次のとおりです。

“[program:sem_com]”は,supervisorが識別するための名称を指定するものです。「sem_com」と命名しました。

「autostart=true」を設定しているので,ラズパイの電源投入やリブート時に自動的に起動されます。

sem_com.pyは,アクティブスキャンやPANA認証に失敗してスマメに接続できないときに終了してしまいます。しかし,「autorestart=true」を設定しているので,スマメとの接続に失敗してプロセスが終了したときや,何らかの不具合によりプロセスが終了したときでも,supervisorが再起動してくれます。

“sem_com.py –delay 15”について説明します。
wwwとsem_com.pyとでは,wwwを先行して起動する必要がありますが,supervisorで自動起動すると,両方がほぼ同時に起動してしまいます。そのため,sem_com.pyに指定した秒数だけ自分自身の起動を遅らせるdelayオプションを設けました。

 

supervisorで2つを起動してみます。”reload”を指定することで,supervisorが2つの設定ファイルを読み込み,その内容に従った処理を行います。
両設定ファイルに「autostart=true」が指定されているので,wwwとsem_com.pyが起動するはずです。

 

ステータスを確認します。

sem_comやsem_appは先ほど設定ファイルの”[program:xxx]”に設定した名称です。

ステータスがRUNNINGになればOKです。そうでなかったら何か設定をミスっていますので,ログファイルを参考に修正しましょう。
ここまでうまくいけば,ラズパイを再起動したときにも同様に動作するはずです。

 

ちなみにsupervisorを使って手動で動作/停止させたいときは,start / stopを使います。

 

7. あとがき

スマメには30分毎の電力量計量値が40日分ほど記憶されているようです。このデータを引っぱってきて…。さらに発展させることも考えましたが,飽きてしましました。よって,ラズパイ+スマートメータープロジェクトを一先ず終了したいと思います。

スマートメーターBルートのプログラミング関連の情報は,今のところそんなに多くはありません。今後,もっと凄い人がもっと面白いアプリケーションを作るための足がかりになれば幸いです。


連載目次

スマートメーターから瞬時電力を取得する その1 

スマートメーターから瞬時電力を取得する その2

スマートメーターから瞬時電力を取得する その3 (本記事)

4 Comments

  1. Pingback: Explorer Hatを使ってみる その2 – Blue-black.ink

  2. もにから

    UDF-1-WSNEを入手してRaspberry piで動かすのに大変参考になりました。
    感謝です。UDF-1-WSNE は SK コマンドが多少違うので多少苦労したのと、
    シリアル周りで時々不具合が起きたのでそれに対応させました。
    通信エラーが意外と多い環境だったので、プロトコルを多少強化しました。
    古い Android2.3機で表示させたかったのですが、javascriptがうまく動かないみたいで、行き詰まり中。成果を送ります。

    ——————————————–ここから
    diff -up org/sem_com.py udg/sem_com.py
    — org/sem_com.py 2016-12-17 15:21:28.327596388 +0900
    +++ udg/sem_com.py 2016-12-24 22:58:55.742941374 +0900
    @@ -85,10 +85,14 @@ class LedThread(threading.Thread):

    def y3reset():
    “””Wi-Sunモジュールのリセット”””
    – gpio.output(Y3RESET_GPIO, gpio.LOW) # high -> low -> high
    – time.sleep(0.5)
    – gpio.output(Y3RESET_GPIO, gpio.HIGH)
    – time.sleep(2.0)
    + if user_conf.UDG1WSNE:
    + y3.clear_sk()
    + y3.set_reset()
    + else:
    + gpio.output(Y3RESET_GPIO, gpio.LOW) # high -> low -> high
    + time.sleep(0.5)
    + gpio.output(Y3RESET_GPIO, gpio.HIGH)
    + time.sleep(2.0)

    class Y3ModuleSub(Y3Module):
    @@ -108,6 +112,11 @@ class Y3ModuleSub(Y3Module):
    if msg:
    msg_list = self.parse_message(msg)

    + #if msg_list[‘COMMAND’] == ‘UNKNOWN’: # debug
    + # sys.stdout.write(‘[UNKNOWN]: {}\n’.format(msg_list[‘MESSAGE’])) # debug
    + #else: # debug
    + # sys.stdout.write(‘[RCV]: {}\n’.format(msg)) # debug
    +
    # debug: UDP(PANA)の受信
    if msg_list[‘COMMAND’] == ‘ERXUDP’ and msg_list[‘LPORT’] == self.Y3_UDP_PANA_PORT:
    sys.stdout.write(‘[PANA]: {}\n’.format(msg_list[‘DATA’]))
    @@ -117,30 +126,43 @@ class Y3ModuleSub(Y3Module):
    and msg_list[‘DATA’][20:22] == self.ECV_INF:
    sem_inf_list.append(msg_list)

    – elif self.search[‘search_words’]: # サーチ中である
    + elif self.search[‘search_words’]: # サーチワードあり
    # サーチワードを受信した。
    search_words = self.search[‘search_words’][0]
    – if isinstance(search_words, list):
    – for word in search_words:
    – if msg_list[‘COMMAND’].startswith(word):
    + if isinstance(search_words, list): # 複数ワードをサーチ中
    + for word in search_words: # サーチワードを順次取出す
    + if msg_list[‘COMMAND’].startswith(word): # 先頭はサーチワードである
    self.search[‘found_word_list’].append(msg_list)
    self.search[‘search_words’].pop(0)
    + if self.search[‘search_words’] == []: # サーチ終了
    + self.search[‘timeout’] = 0
    break
    – elif msg_list[‘COMMAND’].startswith(search_words):
    – self.search[‘found_word_list’].append(msg_list)
    – self.search[‘search_words’].pop(0)
    + else: # 先頭はサーチワードではない
    + if self.search[‘ignore_intermidiate’]: # 先頭がサーチワードではなく、ignore=True である
    + pass # 途中の受信データを破棄
    + else: # 先頭はサーチワードではないが、ignore=False である
    + self.search[‘found_word_list’].append(msg_list) # 途中受信データも記録
    +

    – elif self.search[‘ignore_intermidiate’]:
    – pass # 途中の受信データを破棄

    – else: # サーチワードではなかった
    – self.enqueue_message(msg_list)

    – else: # サーチ中ではない
    + else: # サーチワードは単一である
    + if msg_list[‘COMMAND’].startswith(search_words): # サーチ中のワードは単一で先頭ワードはサーチワードである
    + self.search[‘found_word_list’].append(msg_list)
    + self.search[‘search_words’].pop(0)
    + if self.search[‘search_words’] == []: # サーチ終了
    + self.search[‘timeout’] = 0
    + else:
    + if self.search[‘ignore_intermidiate’]: # 先頭がサーチワードではなく、ignore=True である
    + pass # 途中の受信データを破棄
    + else: # 先頭はサーチワードではないが、ignore=False である
    + self.search[‘found_word_list’].append(msg_list) # 途中受信データも記録
    +
    + else: # write()で管理しない単独メッセージ受信。(y3.write()コマンド投入後のサーチワードなしはここに来ない)
    self.enqueue_message(msg_list)
    + #self.search[‘timeout’] = 0

    elif self.search[‘timeout’]: # read()がタイムアウト,write()でタイムアウトが設定されている
    if time.time() – self.search[‘start_time’] > self.search[‘timeout’]:
    + sys.stdout.write(‘[Error]: Time out. @run\n’) # debug
    self.search[‘found_word_list’] = []
    self.search[‘search_words’] = []
    self.search[‘timeout’] = 0
    @@ -153,17 +175,20 @@ def sem_get(epc):
    frame = sem.GET_FRAME_DICT[‘get_’ + epc]
    tid_counter = tid_counter + 1 if tid_counter + 1 != 65536 else 0 # TICカウントアップ
    frame = sem.change_tid_frame(tid_counter, frame)
    + while y3.get_queue_size(): # 受信バッファのクリア
    + y3.dequeue_message()
    res = y3.udp_send(1, ip6, True, y3.Y3_UDP_ECHONET_PORT, frame)
    + return res

    def sem_get_getres(epc):
    “””プロパティ値要求 ‘Get’, ‘GetRes’受信
    epc: EHONET Liteプロパティ
    “””
    – sem_get(epc) # ‘Get’送信
    + result = sem_get(epc) # ‘Get’送信
    start = time.time()

    – while True:
    + while result:
    if y3.get_queue_size(): # データ受信
    msg_list = y3.dequeue_message() # 受信データ取り出し
    if msg_list[‘COMMAND’] == ‘ERXUDP’:
    @@ -171,18 +196,24 @@ def sem_get_getres(epc):
    if parsed_data[‘tid’] != tid_counter:
    errmsg = ‘[Error]: ECHONET Lite TID mismatch\n’
    sys.stdout.write(errmsg)
    – return False
    – else:
    – return msg_list[‘DATA’]
    + result = False
    + continue
    + else: # 受信成功
    + result = msg_list[‘DATA’]
    + break
    else:
    sys.stdout.write(‘[Error]: Unknown data received.\n’)
    – return False
    + result = False
    + continue

    else: # データ未受信
    if time.time() – start > 20: # タイムアウト 20s
    – sys.stdout.write(‘[Error]: Time out.\n’)
    – return False
    + sys.stdout.write(‘[Error]: Time out. @sem_get_getres\n’)
    + result = False
    + break
    time.sleep(0.01)
    +
    + return result

    def sem_seti(epc, edt):
    @@ -220,7 +251,7 @@ def sem_seti(epc, edt):

    else: # データ未受信
    if time.time() – start > 20: # タイムアウト 20s
    – sys.stdout.write(‘[Error]: Time out.\n’)
    + sys.stdout.write(‘[Error]: Time out. @sem_seti\n’)
    return False
    time.sleep(0.01)

    @@ -250,9 +281,14 @@ def pow_logfile_init(dt):
    if not os.path.exists(csv_filename): # 電力ログ(CSV)が無かったら作成する
    try:
    fcsv = open(csv_filename, ‘w’)
    + if i != 0: # 日付変更以外
    + fcsv.write(‘{},{}\n’.format(round(time.time() – i * 60 * 60 * 24), 0))
    fcsv.close()
    + result = csv2pickle(csv_filename, pkl_filename)
    except:
    return False
    + if not result:
    + return False

    if not os.path.exists(pkl_filename): # 電力ログ(pickle)が無かったら作成する
    result = csv2pickle(csv_filename, pkl_filename)
    @@ -422,7 +458,10 @@ if __name__ == ‘__main__’:
    led.oneshot()

    y3 = Y3ModuleSub()
    – y3.uart_open(dev=’/dev/ttyAMA0′, baud=115200, timeout=1)
    + if user_conf.UDG1WSNE:
    + y3.uart_open(dev=’/dev/ttyACM0′, baud=115200, timeout=1) # for UDG-1-WSNE
    + else:
    + y3.uart_open(dev=’/dev/ttyAMA0′, baud=115200, timeout=1)
    y3.start()
    sys.stdout.write(‘Wi-SUN reset…\n’)

    @@ -444,8 +483,6 @@ if __name__ == ‘__main__’:
    for i in range(10):
    sys.stdout.write(‘({}/10) Active scan with a duration of {}…\n’.format(i+1, user_conf.SEM_DURATION))
    channel_list = y3.active_scan(user_conf.SEM_DURATION)
    – if channel_list is False: # active_scan()をCtrl+cで終了したとき
    – break
    if channel_list:
    sem_exist = True
    break
    @@ -477,27 +514,19 @@ if __name__ == ‘__main__’:
    pana_done = False
    for i in range(10):
    sys.stdout.write(‘({}/10) PANA connection…\n’.format(i+1))
    + sem_inf_list = []
    sem_exist = y3.start_pac(ip6)

    if sem_exist: # インスタンスリスト通知の受信待ち
    – st = time.time()
    – while True:
    – if sem_inf_list:
    – pana_ts = time.time() # タイムスタンプを保存
    – sys.stdout.write(‘Successfully done.\n’)
    – time.sleep(3)
    – pana_done = True
    – break
    – elif time.time() – st > 15: # PANA認証失敗によるタイムアウト
    – sys.stdout.write(‘Fail to connect.\n’)
    – sem_exist = False
    – pana_done = False
    – break
    – else:
    – time.sleep(0.1)

    – if pana_done:
    + pana_ts = time.time() # タイムスタンプを保存
    + sys.stdout.write(‘Successfully done.\n’)
    + time.sleep(3)
    + pana_done = True
    break
    +
    + if not pana_done: # PANA認証失敗
    + sys.stdout.write(‘Fail to connect.\n’)
    + sem_exist = False

    if sem_exist:
    sem = EchonetLiteSmartEnergyMeter()
    @@ -610,8 +639,10 @@ if __name__ == ‘__main__’:

    if sem_exist:
    start = time.time() – 1000 # 初期値を1000s前に設定
    + time_period = time.time()
    while True:
    try:
    + “”” モジュールが自動で再認証しているので削除 (SKSREG S17)
    pana_done = False
    if (time.time() – pana_ts > 12 * 60 * 60): # 12時間毎にPANA認証を更新
    sys.stdout.write(‘PANA re-connection…\n’)
    @@ -634,6 +665,7 @@ if __name__ == ‘__main__’:

    if not pana_done:
    break # PANA認証失敗でbreakする
    + “””

    while True:
    if (time.time() – start) >= user_conf.SEM_INTERVAL:
    @@ -642,9 +674,11 @@ if __name__ == ‘__main__’:
    else:
    time.sleep(0.1)

    – sem_get(‘instant_power’) # Get
    + if time.time() – time_period > 30: # 1分間GetRes失敗
    + y3.clear_sk() # SKコマンドバッファクリア
    + rec = sem_get(‘instant_power’) # Get

    – while True: # GetRes待ちループ
    + while rec: # GetRes待ちループ
    rcd_time = time.time() # rcd_time[s]
    new_dt = datetime.datetime.fromtimestamp(rcd_time)

    @@ -667,6 +701,7 @@ if __name__ == ‘__main__’:
    watt_int = int.from_bytes(parsed_data[‘ptys’][0][‘edt’], ‘big’, signed=True)
    sys.stdout.write(‘[{:5d}] {:4d} W\n’.format(tid_counter, watt_int))
    sys.stdout.flush()
    + time_period = time.time()

    try: # 一時ログファイルに書き込み
    f = open(TMP_LOG_FILE, ‘a’) # rcd_time[ms] (JavaScript用)
    @@ -696,9 +731,18 @@ if __name__ == ‘__main__’:
    inf = sem_inf_list.pop(0)
    sys.stdout.write(‘[Inf]: {}\n’.format(inf[‘DATA’]))

    – if time.time() – start > 20: # GetRes最大待ち時間: 20s
    – sys.stdout.write(‘[Error]: Time out.\n’)

    + if time.time() – start > 10: # GetRes最大待ち時間: 10s
    + sys.stdout.write(‘[Error]: Time out. @arg_parse\n’)
    + y3.clear_sk()
    + try: # 一時ログファイルに書き込み
    + f = open(TMP_LOG_FILE, ‘a’)
    + f.write(‘{},None\n’.format(round(rcd_time)))
    + f.close()
    + except:
    + sys.stdout.write(‘[Error]: can not write to file.\n’)
    + break
    + elif rec == False: # 通信エラー
    + y3.clear_sk()
    try: # 一時ログファイルに書き込み
    f = open(TMP_LOG_FILE, ‘a’)
    f.write(‘{},None\n’.format(round(rcd_time)))
    diff -up org/user_conf.py udg/user_conf.py
    — org/user_conf.py 2016-12-22 22:53:41.765736097 +0900
    +++ udg/user_conf.py 2016-12-24 22:58:55.742941374 +0900
    @@ -7,7 +7,8 @@
    # Copyright(C) 2016 pi@blue-black.ink
    #

    -SEM_ROUTEB_ID = ‘00000000000000000000000000000000’
    -SEM_PASSWORD = ‘XXXXXXXXXXXX’
    +SEM_ROUTEB_ID = ‘00000000000000000000000000000000’
    +SEM_PASSWORD = ‘XXXXXXXXXXXX’
    SEM_INTERVAL = 3 # 瞬時電力取得間隔[s]
    SEM_DURATION = 6 # アクティブスキャンduration (通常は変更の必要なし)
    +UDG1WSNE = 1 # 0:BP35A1 1: UDG-1-WSNE
    diff -up org/y3module.py udg/y3module.py
    — org/y3module.py 2016-12-17 15:21:01.827809202 +0900
    +++ udg/y3module.py 2016-12-24 22:58:55.742941374 +0900
    @@ -12,7 +12,7 @@ import serial
    import threading
    import time
    import sys

    +import user_conf

    class Y3Module(threading.Thread):
    “””Wi-SUN Module BP35A1(ROHM) 通信クラス”””
    @@ -28,7 +28,7 @@ class Y3Module(threading.Thread):

    self.uart_hdl = None # UART
    self.uart_dev = None
    – self.uart_baud = 9600
    + self.uart_baud = 115200

    self.search = { # write()用, UART送信後の受信待ちデータ
    ‘search_words’: [], # UART送信後の受信待ちデータリスト
    @@ -40,11 +40,40 @@ class Y3Module(threading.Thread):
    self.msg_list_lock = threading.Lock() # msg_listの排他制御用

    + def set_reset(self):
    + “””SKRESET”””
    + res = self.write(b’SKRESET\r\n’, [[‘OK’, ‘FAIL’]], ignore = True, timeout = 3)
    + return
    +
    +
    + def clear_sk(self):
    + “””コマンド入力バッファクリア”””
    + res = self.write(b’\r\n’)
    + return
    +
    +
    + def get_ver(self):
    + “””SKVER”””
    + res = self.write(b’SKVER\r\n’, [‘EVER’, ‘OK’], ignore = True, timeout = 3)
    + return res[0][‘VERSION’]
    +
    +
    + def req_echo(self, ip6):
    + “””SKPING”””
    + if user_conf.UDG1WSNE:
    + res = self.write(b’SKPING 0 ‘ + ip6.encode() + b’\r\n’, [‘OK’, ‘EPONG’], ignore = True, timeout = 3)
    + else:
    + res = self.write(b’SKPING ‘ + ip6.encode() + b’\r\n’, [‘OK’, ‘EPONG’], ignore = True, timeout = 3)
    + return res
    +
    +
    def set_opt(self, flag):
    “””ERXUDP, ERXTCPのフォーマット設定
    flag: True: ASCII
    False: Binary
    “””
    + if user_conf.UDG1WSNE:
    + return True
    current = self.get_opt()
    if flag and not current: # 変更無しの場合はモジュールに書き込まない(FLASHへの書き込み制限)
    self.write(b’WOPT 01\r\n’, [‘OK 01′])
    @@ -58,6 +87,8 @@ class Y3Module(threading.Thread):
    retern True: ASCII
    False: Binary
    “””
    + if user_conf.UDG1WSNE:
    + return True
    res = self.write(b’ROPT\r\n’, [‘OK’])
    return True if res[0][‘MESSAGE’][0] == ’01’ else False

    @@ -68,19 +99,46 @@ class Y3Module(threading.Thread):

    def set_channel(self, ch):
    – “””Wi-SUNチャンネル設定”””
    – bc = ‘{:02X}’.format(ch).encode()
    – self.write(b’SKSREG S02 ‘ + bc + b’\r\n’, [‘OK’])
    + “””Wi-SUNチャンネル設定(保存される)”””
    + current = self.get_channel()
    + if current != ch:
    + bc = ‘{:02X}’.format(ch).encode()
    + self.write(b’SKSREG S02 ‘ + bc + b’\r\n’, [‘OK’])
    +
    +
    + def get_channel(self):
    + “””Wi-SUNチャンネル取得
    + ESREG イベントで通知を受ける
    + “””
    + res = self.write(b’SKSREG S02′ + b’\r\n’, [‘ESREG’, ‘OK’], True, 3)
    + if res:
    + return res[0][‘VAL’]
    + else:
    + return False

    def set_pairing_id(self, pairid):
    “””ペアリングID設定”””
    self.write(b’SKSREG S0A ‘ + pairid.encode() + b’\r\n’, [‘OK’])

    +
    def set_pan_id(self, pan):
    – “””PAN ID設定”””
    – bp = ‘{:04X}’.format(pan).encode()
    – self.write(b’SKSREG S03 ‘ + bp + b’\r\n’, [‘OK’])
    + “””PAN ID設定(保存される)”””
    + current = self.get_pan_id()
    + if current != pan:
    + bp = ‘{:04X}’.format(pan).encode()
    + self.write(b’SKSREG S03 ‘ + bp + b’\r\n’, [‘OK’])
    +
    +
    + def get_pan_id(self):
    + “””PAN ID設定
    + ESREG イベントで通知を受ける
    + “””
    + res = self.write(b’SKSREG S03′ + b’\r\n’, [‘ESREG’, ‘OK’], True, 3)
    + if res:
    + return res[0][‘VAL’]
    + else:
    + return False

    def set_accept_beacon(self, flag):
    @@ -94,11 +152,17 @@ class Y3Module(threading.Thread):

    def get_tx_limit(self):
    “””送信制限フラグ取得”””
    – res = self.write(b’SKSREG SFB\r\n’ , [‘ESREG’, ‘OK’])
    – result = True if res[0][‘VAL’][0] == ‘1’ else False
    + res = self.write(b’SKSREG SFB\r\n’ , [‘ESREG’, ‘OK’], True, 3)
    + result = True if res[0][‘VAL’] == ‘1’ else False
    return result

    + def set_icmp_ctrl(self, flag):
    + “””ICMP メッセージ処理制御”””
    + bf = b’1′ if flag else b’0′
    + res = self.write(b’SKSREG SA1 ‘ + bf + b’\r\n’, [‘OK’])
    +
    +
    def set_password(self, password):
    “””パスワード設定”””
    length = len(password)
    @@ -123,43 +187,50 @@ class Y3Module(threading.Thread):

    def start_paa(self):
    “””PAA開始”””
    – self.write(b’SKSTART\r\n’, [‘OK’])
    + self.write(b’SKSTART\r\n’, [‘OK’], True, 10)

    def start_pac(self, ip6):
    “””PaC開始”””
    – res = self.write(b’SKJOIN ‘ + ip6.encode() + b’\r\n’, [[‘EVENT 24’, ‘EVENT 25’, ‘FAIL ER10’]],
    – ignore = True, timeout = 10)
    + res = self.write(b’SKJOIN ‘ + ip6.encode() + b’\r\n’, [[‘EVENT 24’, ‘EVENT 25’, ‘FAIL’]],
    + ignore = True, timeout = 30)
    + if not res: # time out
    + return False
    try:
    result = True if res[0][‘COMMAND’] == ‘EVENT 25′ else False
    return result
    except: # IndexErrorが発生するときのための暫定処理。要検討
    – result = False
    + return False
    +



    def restart_pac(self):
    “””PaCをリスタート”””
    – res = self.write(b’SKREJOIN\r\n’, [[‘EVENT 24’, ‘EVENT 25’, ‘FAIL ER10′]], ignore = True, timeout = 10)
    + res = self.write(b’SKREJOIN\r\n’, [[‘EVENT 24’, ‘EVENT 25’, ‘FAIL’]], ignore = True, timeout = 30)
    + if not res: # time out
    + return False
    try:
    result = True if res[0][‘COMMAND’] == ‘EVENT 25′ else False
    return result
    except: # IndexErrorが発生するときのための暫定処理。要検討
    – result = False
    + return False
    +


    def pac_terminate(self):
    “””PANAセッションを終了する”””
    – res = self.write(b’SKTERM\r\n’, [[‘OK’, ‘FAIL ER10′]], ignore = True, timeout = 10)
    + res = self.write(b’SKTERM\r\n’, [[‘OK’, ‘FAIL’, ‘EVENT 27’, ‘EVENT 28’]], ignore = True, timeout = 10)
    + if not res: # time out
    + return False
    if res[0][‘COMMAND’] == ‘OK’:
    return True
    else:
    return False

    +

    def get_ip6(self, add):
    “””IP6アドレス習得”””
    res = self.write(b’SKLL64 ‘ + add.encode() + b’\r\n’, [‘UNKNOWN’])
    + if not res: # time out
    + return False
    return res[0][‘MESSAGE’][0]

    @@ -180,7 +251,10 @@ class Y3Module(threading.Thread):
    def tcp_send(self, handle, message):
    “””TCPで送信”””
    len_bt =’ {:04X} ‘.format(len(message)).encode()
    – res = self.write(b’SKSEND ‘ + str(handle).encode() + len_bt + message, [‘ETCP’])
    + if user_conf.UDG1WSNE:
    + res = self.write(b’SKSEND ‘ + str(handle).encode() + len_bt + ‘ 0’ + message, [‘ETCP’]) # for UDG-1-WSNE
    + else:
    + res = self.write(b’SKSEND ‘ + str(handle).encode() + len_bt + message, [‘ETCP’])
    return res[0][‘STATUS’] == 5

    @@ -189,22 +263,39 @@ class Y3Module(threading.Thread):
    sec_bt = b’ 1′ if security else b’ 0′
    len_bt = ‘ {:04X} ‘.format(len(message)).encode()
    port_bt = ‘ {:04X}’.format(port).encode()
    – res = self.write(b’SKSENDTO ‘ + str(handle).encode() + b’ ‘ + ip6.encode() + port_bt +
    + if user_conf.UDG1WSNE:
    + res = self.write(b’SKSENDTO ‘ + str(handle).encode() + b’ ‘ + ip6.encode() + port_bt +
    + sec_bt + b’ 0’+ len_bt + message, [‘EVENT 21’, ‘OK’]) # for UDG-1-WSNE
    + else:
    + res = self.write(b’SKSENDTO ‘ + str(handle).encode() + b’ ‘ + ip6.encode() + port_bt +
    sec_bt + len_bt + message, [‘EVENT 21’, ‘OK’])

    – if res[0][‘PARAM’] == ’01’:
    – sys.stdout.write(‘[Error]: UDP transmission.\n’)
    – if self.get_tx_limit():
    – sys.stdout.write(‘[Error]: TX limit.\n’)
    + if not res: # time out
    + self.clear_sk()
    return False
    – else:
    – return True # 送信成功
    +
    + result = False
    + for i in range(len(res)):
    + if res[i][‘COMMAND’] == ‘EVENT 21’:
    + if res[i][‘PARAM’] == ’00’: # 送信成功
    + result = True
    + break
    + elif res[i][‘PARAM’] == ’01’: # 送信失敗
    + sys.stdout.write(‘[Error]: UDP transmission.\n’)
    + if self.get_tx_limit():
    + sys.stdout.write(‘[Error]: TX limit.\n’)
    + break
    +
    + return result

    def ed_scan(self, duration = 4):
    “””EDスキャン”””
    bd = ‘{:X}’.format(duration).encode()
    – self.write(b’SKSCAN 0 FFFFFFFF ‘ + bd + b’\r\n’, [[‘EEDSCAN’], [‘OK’]])
    + if user_conf.UDG1WSNE:
    + self.write(b’SKSCAN 0 FFFFFFFF ‘ + bd + b’ 0′ + b’\r\n’, [[‘EEDSCAN’], [‘OK’]], ignore = False, timeout = 40) # for UDG-1-WSNE
    + else:
    + self.write(b’SKSCAN 0 FFFFFFFF ‘ + bd + b’\r\n’, [[‘EEDSCAN’], [‘OK’]], ignore = False, timeout = 40)
    res = []
    while True:
    if self.get_queue_size():
    @@ -224,41 +315,38 @@ class Y3Module(threading.Thread):
    def active_scan(self, duration = 6):
    “””アクティブスキャン”””
    bd = ‘{:X}’.format(duration).encode()
    – self.write(b’SKSCAN 2 FFFFFFFF ‘ + bd + b’\r\n’)
    + if user_conf.UDG1WSNE:
    + res = self.write(b’SKSCAN 2 FFFFFFFF ‘ + bd + b’ 0′ + b’\r\n’, [‘EVENT 22’], False, 40) # for UDG-1-WSNE
    + else:
    + res = self.write(b’SKSCAN 2 FFFFFFFF ‘ + bd + b’\r\n’, [‘EVENT 22’], False, 40)
    +
    + “”” res ← search[found_word_list] “””
    +
    scan_end = False
    channel_list = []
    channel = {}

    – try:
    – while not scan_end:
    – if self.get_queue_size():
    – msg_list = self.dequeue_message()
    – if msg_list[‘COMMAND’] == ‘EVENT 20’:
    – pass # beacon 受信
    – elif msg_list[‘COMMAND’] == ‘EPANDESC’:
    – channel = {}
    – elif msg_list[‘COMMAND’] == ‘ACTIVESCAN’:
    – if ‘Channel’ in msg_list:
    – channel[‘Channel’] = msg_list[‘Channel’]
    – elif ‘Channel Page’ in msg_list:
    – channel[‘Channel Page’] = msg_list[‘Channel Page’]
    – elif ‘Pan ID’ in msg_list:
    – channel[‘Pan ID’] = msg_list[‘Pan ID’]
    – elif ‘Addr’ in msg_list:
    – channel[‘Addr’] = msg_list[‘Addr’]
    – elif ‘LQI’ in msg_list:
    – channel[‘LQI’] = msg_list[‘LQI’]
    – elif ‘PairID’ in msg_list:
    – channel[‘PairID’] = msg_list[‘PairID’]
    – channel_list.append(channel)
    – elif msg_list[‘COMMAND’] == ‘EVENT 22’:
    – scan_end = True
    – else:
    – time.sleep(0.01)
    – except KeyboardInterrupt:
    – channel_list = False # スキャンキャンセル

    – return channel_list
    + for msg_list in res:
    + if msg_list[‘COMMAND’] == ‘EVENT 20’:
    + pass # beacon 受信
    + elif msg_list[‘COMMAND’] == ‘EPANDESC’:
    + channel = {}
    + elif msg_list[‘COMMAND’] == ‘ACTIVESCAN’:
    + if ‘Channel’ in msg_list:
    + channel[‘Channel’] = msg_list[‘Channel’]
    + elif ‘Channel Page’ in msg_list:
    + channel[‘Channel Page’] = msg_list[‘Channel Page’]
    + elif ‘Pan ID’ in msg_list:
    + channel[‘Pan ID’] = msg_list[‘Pan ID’]
    + elif ‘Addr’ in msg_list:
    + channel[‘Addr’] = msg_list[‘Addr’]
    + elif ‘LQI’ in msg_list:
    + channel[‘LQI’] = msg_list[‘LQI’]
    + elif ‘PairID’ in msg_list:
    + channel[‘PairID’] = msg_list[‘PairID’]
    + channel_list.append(channel)
    + elif msg_list[‘COMMAND’] == ‘EVENT 22’:
    + return channel_list
    + return False

    @staticmethod
    @@ -313,9 +401,13 @@ class Y3Module(threading.Thread):
    if cols[0] == ‘EVENT’:
    msg_list[‘COMMAND’] = cols[0] + ‘ ‘ + cols[1]
    msg_list[‘SENDER’] = cols[2]
    – if len(cols) == 4:
    – msg_list[‘PARAM’] = cols[3]

    + if user_conf.UDG1WSNE:
    + if len(cols) == 5: # UDG-1-WSNE
    + msg_list[‘PARAM’] = cols[4] # UDG-1-WSNE
    + else:
    + if len(cols) == 4:
    + msg_list[‘PARAM’] = cols[3]
    +
    return msg_list

    if cols[0] == ‘ERXUDP’: # UDP
    @@ -326,8 +418,13 @@ class Y3Module(threading.Thread):
    msg_list[‘LPORT’] = int(cols[4], base=16)
    msg_list[‘SENDERLLA’] = cols[5]
    msg_list[‘SECURED’] = int(cols[6], base=16)
    – msg_list[‘DATALEN’] = int(cols[7], base=16)
    – msg_list[‘DATA’] = cols[8]
    + if user_conf.UDG1WSNE:
    + msg_list[‘DATALEN’] = int(cols[8], base=16)
    + msg_list[‘DATA’] = cols[9]
    + else:
    + msg_list[‘DATALEN’] = int(cols[7], base=16)
    + msg_list[‘DATA’] = cols[8]
    +
    return msg_list

    if cols[0] == ‘ERXTCP’:
    @@ -362,6 +459,36 @@ class Y3Module(threading.Thread):
    msg_list[‘COMMAND’] = ‘EEDSCAN’
    return msg_list

    + if cols[0] == ‘EPONG’:
    + msg_list[‘COMMAND’] = cols[0]
    + if user_conf.UDG1WSNE:
    + msg_list[‘SENDER’] = cols[2]
    + else:
    + msg_list[‘SENDER’] = cols[1]
    + return msg_list
    +
    + if cols[0] == ‘EVER’:
    + msg_list[‘COMMAND’] = cols[0]
    + msg_list[‘VERSION’] = cols[1]
    + return msg_list
    +
    + if cols[0] == ‘FAIL’:
    + msg_list[‘COMMAND’] = cols[0]
    + msg_list[‘ERRCODE’] = cols[1]
    + return msg_list
    +
    + if cols[0] == ‘EINFO’:
    + msg_list[‘IPADDR’] = cols[0]
    + msg_list[‘ADDR64’] = cols[1]
    + msg_list[‘CANNEL’] = cols[2]
    + msg_list[‘PANID’] = cols[3]
    + msg_list[‘ADDR16’] = cols[4]
    + return msg_list
    +
    + # EADDR [IPADDR]
    + # ENEIGHBOR [IPADDR ADDR64 ADDR16]
    + # EPORT
    + #
    # ローカルエコー停止前のローカルエコー対策: ‘SKSREG SFE 0’
    if cols[0] == ‘SKSREG’:
    msg_list[‘COMMAND’] = ‘SKSREG’
    @@ -422,7 +549,7 @@ class Y3Module(threading.Thread):
    sys.stdout.write(‘[Error]: {}\n’.format(msg))

    – def write(self, send_msg, search_words = [], ignore = False, timeout = 0):
    + def write(self, send_msg, search_words = [], ignore = False, timeout = 10):
    “””UART書き込み & 受信待ち
    send_msg: 送信データ: bytes
    search_word: 受信待ちコマンド
    @@ -431,20 +558,30 @@ class Y3Module(threading.Thread):
    timeout: タイムアウト時間[s]
    “””
    try:
    + #sys.stdout.write(‘[SND]: {}, timeout = {}\n’.format(send_msg, timeout)) # debug
    + self.search[‘ignore_intermidiate’] = ignore
    + self.search[‘start_time’] = time.time()
    self.uart_hdl.write(send_msg)
    + self.search[‘found_word_list’] = []
    if search_words:
    – self.search[‘found_word_list’] = []
    – self.search[‘ignore_intermidiate’] = ignore
    – self.search[‘start_time’] = time.time()
    + #self.search[‘found_word_list’] = []
    + #self.search[‘ignore_intermidiate’] = ignore
    self.search[‘timeout’] = timeout
    self.search[‘search_words’] = search_words # run()で監視しているので、一番最後に設定する

    – while self.search[‘search_words’] != []:
    + while self.search[‘search_words’] != []: # run()でself.search[‘search_words’]をpop(0)してゆく
    time.sleep(0.01)
    +
    + self.search[‘timeout’] = 0
    return self.search[‘found_word_list’]
    + else:
    + self.search[‘timeout’] = 0
    + self.search[‘search_words’] = []
    + return

    except OSError as msg:
    sys.stdout.write(‘[Error]: {}\n’.format(msg))
    + self.search[‘timeout’] = 0
    return False

    @@ -452,13 +589,14 @@ class Y3Module(threading.Thread):
    “””1行読み込み(文字列)”””
    try:
    res = self.uart_hdl.readline().decode().strip()
    – #print(‘read:’+res) # debug
    + #if res: print(‘read:’+res) # debug
    return res
    except OSError as msg:
    sys.stdout.write(‘[Error]: {}\n’.format(msg))
    return False

    + ”’
    def run(self):
    “””UART受信用スレッド”””
    while not self.term_flag:
    @@ -498,6 +636,7 @@ class Y3Module(threading.Thread):
    self.search[‘found_word_list’] = []
    self.search[‘search_words’] = []
    self.search[‘timeout’] = 0
    + ”’

    def terminate(self):
    —————————————————-ここまで

    Reply
    1. pi (Post author)

      もにからさん

      スマメBルートはUDPなので、通信エラーを前提にコーディングしなくてはならないので難しいですね。
      かなり汚いコードになっているので恥ずかしい限りですが、少しでもお役に立てて良かったです。

      Reply
  3. もにから

    投稿時、エラーとなり、投稿できていないと思っていたのでインデントが崩れに気づきませんでした。使い物になりませんね。すみません。こちらに https://www.axfc.net/u/3769355 パッチを置きました。パスワードは、 udg1wsne です。user_conf.py の UDG1WSNE = 0 とすると BP35A1 で使えるはずです。12月末から udg-1-wsne で稼働中です。古いandroid機では表示できませんでしたが、使っていなかったiphone4sで表示しています。

    Reply

Leave a Comment

メールアドレスが公開されることはありません。