編集履歴一覧に戻る
84motのアイコン画像

84mot が 2022年09月25日20時41分46秒 に編集

初版

タイトルの変更

+

家庭の消費電力値をスマートメータから取得してEVの充電タイミングを制御したいデバイス

タグの変更

+

spresense

+

Wi-SUN

記事種類の変更

+

製作品

本文の変更

+

概要 --- SONYのSpresenseとROHMのWI-SUN拡張モジュールを使って家庭のスマートメータ―から現在の消費電力値を取得して、EVなどの大電力消費デバイスの使用可否を判断できるデバイスを作りました。 解決したいこと --- 最近アーク溶接機を購入しましたが、いざ家で溶接しようとすると気になるのが消費電力。 どれくらい電力を使っているか正直よくわかっていません。 いつも恐る恐る使っていますが、自宅で使用しているEVの充電タイミングとかち合ってブレーカー落すなんて事は回避したい。(家ではデスクトップで仕事もしていることですし) 欲しいもの --- しかしながら **「今我が家はどれくら電力を消費しているのか」** を把握するのはかなりハードルが高い。 そしてブレーカーを落とす前に優先度の低い機器のスイッチを切ってほしい。←EVの充電とか。 それも自動で。 どうやって実現するか --- Spresenseを基本として以下を追加して構成。   WI-sun拡張ボード (家庭用スマート電力メーターからの瞬間電力値取得用)   リレー (充電コードの根本を直接ON/OFFする) これであれば既存のEV充電器にリレーをかまして、心置きなく溶接を楽しめるようなシステムが作れるはず! 以下がその構成図 ![構成図](https://camo.elchika.com/58f462975185db484c821e15f9611fcaaf4890fe/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63643032363933382d386265652d346364362d613266662d3963663466613233636662612f36313266613762332d326361312d346334342d626534622d303437316431363032343963/) が、実際に作成したものは大きく変更が入りました。 ~背景(という名の言い訳)~ 開発にあたり真っ先にWI-SUN経由でスマートメータから電力情報を取得するところから取り掛かりました。 が・・・想像の100倍大変でした。 基本的にはハードさえあれば、後は自宅の管轄の電力会社からもらえるメーター接続用のIDとパスワードがあれば必要なものは揃います。 これはスムーズに入手できました。 が、その後のソフト開発で難航‥‥。 ハードのWI-SUNボードの製造元(ROHM)のサンプルコードはあるのですが、それは拡張ボードを子機(スマートメータ側)として使用する場合のコードとなっていました。(←これに気づくのにも時間がかかった…) 拡張ボード”から”メーターにアクセスする際のサンプルコードを見つける事ができず、結局用途違いではありますが前述のROHMのサンプルコードをベースに組みなおしました。 スマートメータ接続用のプロトコルであるECHONET-Liteも馴染みが薄く、情報収集もなかなか進まない中で仕様書とにらめっこしながら実装を進める事10日間。 なんとか瞬時電力値の取得に成功しました!というのが締め切り2日前。。。 実際に作成した構成図は以下の通り。 ![実際に作った構成図](https://camo.elchika.com/37cd20a44c83775b155dee3c345e33adac1edf15/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f63643032363933382d386265652d346364362d613266662d3963663466613233636662612f37643239396537382d643561372d346238622d396463642d323235626134353539633832/) 動作 --- WI-sun拡張ボードがスマートメータ―から常時消費電力値を取得。 家全体の消費電力が一定の数値を超えるとサーボを駆動して車の模型からEV充電プラグを外して充電禁止をユーザーに通知。 一定以内であれば電力消費量に余裕ありとして車の模型に充電プラグを接続して、充電OKをユーザーに通知。 溶接を行なう時もEVの充電プラグが模型と同じ状態かを確認すれば、ブレーカーを飛ばす事も無い・・・はず。 参考URL --- まさか情報集でもこんなに苦労するとは…という状況の中で最終的に輝いていた仕様書を記載します。 ECHONET規格団体 [https://echonet.jp/spec_object_rq/](https://echonet.jp/spec_object_rq/) 細かい通信仕様(ここの理解が超重要) [https://echonet.jp/spec_v113_lite/](https://echonet.jp/spec_v113_lite/) ROHM(Wi-SUNのIC製造元)が出しているスマートメータと接続するためのコマンドやり取り。 これが神の様な役割を果たし、電力値の取得までいけました。感謝。 ※これ見て実装するだけ…と言えばその通りなんですが、ハードもソフトもハマリポイント多し) [https://fscdn.rohm.com/jp/products/databook/applinote/module/wireless/bp35c0-j11_b-route_an-j.pdf](https://fscdn.rohm.com/jp/products/databook/applinote/module/wireless/bp35c0-j11_b-route_an-j.pdf) プログラム --- 最後にプログラムを記載しておきます。 ※デバッグ分も多いため非常に長いプログラムとなってしまっていますがご勘弁ください 今回はArduino IDEで開発しました。 以下の3ファイルで構成されています。 ・main.ino    シーケンス制御を主として行なう。    state=0から始まり、state=9で瞬時電力の取得を繰り返す ・SPRESENSE-WISUN-EVK-701_84mota-2.ino    メインの処理。    コマンドの送信、応答の処理など。 ・bp35c0-j11.h    Wi-SUNやECHONET-Liteに関係するコマンドなどの定義。 ```#define DEBUG``` のコメント/コメントアウトによりSerial上に各種状態などが大量に出力されます。 SPRESENSE-WISUN-EVK-701_84mota-2.ino --- ```arduino : SPRESENSE-WISUN-EVK-701_84mota-2.ino /* SPRESENSE-WISUN-EVK-701 Copyright (c) 2019 ROHM Co.,Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "bp35c0-j11.h" //#define DEBUG #define DEBUG2 unsigned char state = 0; unsigned char state_past = 0; BP35C0J11 bp35c0j11; void setup() { boolean rc = FALSE ; bp35c0j11.j11_init(); rc = bp35c0j11.wait_msg(); #ifdef DEBUG Serial.println("wait_mes() 通過"); Serial.print("rc="); Serial.println(rc); #endif if (rc == TRUE) { state = 1 ; // hardware reset end } else { state = 0 ; } //シリアルモニタ用 Serial.println("瞬時電力 [W]"); s_servo.write(90); //サーボ駆動用 s_servo.attach(PIN_D03); s_servo.write(80); } void loop() { unsigned char msg_length = 0 ; boolean rc = 0 ; #ifdef DEBUG // Serial.println(" "); // Serial.print("State = "); // Serial.println(state, DEC); #endif delay(500); #ifdef DEBUG2 if(state != state_past){ Serial.print("state="); Serial.println(state); } state_past = state; #endif switch (state) { case (0): // need hardware reset rc = bp35c0j11.cmd_send(CMD_RESET); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { state = 1 ; } break; case (1): // init state rc = bp35c0j11.cmd_send(CMD_INI); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { state = 2; } break; case (2): //B-root 接続初期設定 //Bルート接続認証ステータスを追加 ここから rc = bp35c0j11.cmd_send(CMD_B_ROOT_INI); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { state = 3; } break; //Bルート接続認証ステータスを追加 ここまで case (3): // active scan rc = bp35c0j11.cmd_send(CMD_SCAN); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { rc = bp35c0j11.wait_msg(); } break; case (4): // スキャン結果をBルート設定にフィードバック rc = bp35c0j11.cmd_send(CMD_INI2); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { state = 5; #ifdef DEBUG Serial.println("スキャン結果の取り込み完了"); #endif DEBUG } break; case (5): // Bルート動作開始要求 rc = bp35c0j11.cmd_send(CMD_B_ROOT_ACTIVE); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { #ifdef DEBUG Serial.println("Bルート動作を開始します"); #endif DEBUG state = 6; } break; case (6): // UDPポートOPEN要求 rc = bp35c0j11.cmd_send(CMD_PORTOPEN); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { #ifdef DEBUG Serial.println("status を7に変更"); #endif DEBUG state = 7; } break; case (7): // PANA認証開始 rc = bp35c0j11.cmd_send(CMD_B_ROOT_PANA); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { rc = bp35c0j11.wait_msg(); if (rc == TRUE) { rc = bp35c0j11.wait_msg(); if (rc == 1) { #ifdef DEBUG Serial.println("PANA認証開始応答を確認。state を8に変更"); #endif DEBUG state = 8; } else { #ifdef DEBUG Serial.println("PANA認証開始応答を確認できませんでした。再起動します。"); #endif DEBUG state = 0; } } } break; case (8): //スマートメータ―応答値の定義を確認する。 //動作状態、係数、積算電力量有効桁数、積算電力量単位 rc = bp35c0j11.cmd_send(CMD_UDPSEND ); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { #ifdef DEBUG Serial.println("データ送信要求 完了"); Serial.println("メーターからの応答待ち"); #endif DEBUG rc = bp35c0j11.wait_msg(); if (rc == TRUE) { #ifdef DEBUG Serial.println("statusを9に変更"); #endif DEBUG state = 9; } else { state = 0; Serial.println("メーターからの応答がタイムアウトしたため再起動します"); } } else { Serial.println("データ送信要求 失敗"); state = 8; } break; case (9): //■■■計測時刻付きで積算電力計測値を取得する(30分ごと更新値)■■■ //■■■瞬時電力値、瞬時電流値を取得する■■■ rc = bp35c0j11.cmd_send(CMD_UDPSEND ); rc = bp35c0j11.wait_msg(); if (rc == TRUE) { #ifdef DEBUG Serial.println("データ送信要求 完了"); Serial.println("メーターからの応答待ち"); #endif DEBUG rc = bp35c0j11.wait_msg(); if (rc == TRUE) { #ifdef DEBUG Serial.println("全シーケンス終了。再度計測します"); #endif DEBUG state = 9; } else { Serial.println("メーターからの応答がタイムアウトしたため再要求を実施します"); } } else { Serial.println("データ送信要求 失敗"); } break; default : break; } } ``` bp35c0-j11.cpp --- ```arduino : bp35c0-j11.cpp /******************************************************************************** bp35c0-j11.cpp Copyright (c) 2019 ROHM Co.,Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *********************************************************************************/ #include <Arduino.h> #include "bp35c0-j11.h" //#define DEBUG unsigned const char uni_req[4] = {0xD0 , 0xEA , 0x83 , 0xFC};//Wi-SUNコマンド デバイスへの要求コマンド定義 unsigned const char uni_res[4] = {0xD0 , 0xF9 , 0xEE , 0x5D};//Wi-SUNコマンド デバイスからの応答コマンド定義 unsigned const char ini_data[4] = {0x05 , 0x00 , 0x04 , 0x00}; // Dual(Bルート&HAN) /Sleep 非対応/922.9MHz/20mW出力 unsigned const char scan_CMD[6] = {0x06, 0x00, 0x03, 0xFF, 0xF0, 0x01}; // Active Scan 設定 >> スキャン時間 9.65ms*64 , 4~17CHスキャン, Paring ID 有り の設定 unsigned char pair_id[8] = {0}; // 接続先MACアドレス unsigned char pan_id[2]; // スマートメータ PAN ID unsigned char scan_ch[1]; //スキャンした結果スマートメーターからの応答のあったチャンネルを格納する unsigned char IPv6_adr[16] = {0}; // 接続先IPv6アドレス スキャンした際にスマートメータ―からの応答を格納する unsigned const char my_port[2] = { 0x0E , 0x1A }; // オープンするUDPポート unsigned const char dist_port[2] = { 0x0E , 0x1A }; // 送信先UDPポート //電力会社から送られてきた情報をそのまま打ち込む //b_root_id → 認証ID unsigned const char b_root_id[32] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',}; // //Bルート認証IDをフル桁 unsigned const char password[12] = { 'A' , 'B' , 'C' , 'D' , 'E', 'F' , 'G', 'H' , 'I' , 'J' , 'K' , 'L' }; // パスワード12桁 float Watt = 0 ; //瞬時電力値 [W] float Watt_th = 1000; // 瞬時電力値からEV充電可否判断のための閾値 [W] //サーボ駆動用 int start_time = 0; int change_time = 500; // サーボの角度遷移時間 [ms] CMD_FORMAT cmd_format; BP35C0J11::BP35C0J11(void) { } /******************************************************************************** Name : j11_init Function : initial setting bp35c0-j11 input : - return : - *********************************************************************************/ void BP35C0J11::j11_init(void) { // configure output D20/D21 pinMode(PIN_ENABLE, OUTPUT); pinMode(PIN_RESET, OUTPUT); digitalWrite(PIN_ENABLE, HIGH); delay(1000); // Serial port initial Serial2.begin(115200); Serial.begin(115200); #ifdef DEBUG Serial.write("RESET"); Serial.println(""); #endif digitalWrite(PIN_RESET, LOW); // reset delay(500); digitalWrite(PIN_RESET, HIGH); } /******************************************************************************** Name : wait_msg Function : wait for response from bp35c0-j11 input : - return : TRUE/FALSE *********************************************************************************/ boolean BP35C0J11::wait_msg(void) { unsigned long start_time; unsigned long current_time; unsigned char rcvdata[128] = {0} ; unsigned char cnt = 0 ; start_time = millis(); while (Serial2.available() == 0) { current_time = millis(); if ((current_time - start_time) > TIMEOUT) { Serial.println("receive timeout -> hardware reest"); state = 0; return FALSE; } } while (Serial2.available() > 0 ) { delay(5); rcvdata[cnt] = Serial2.read(); #ifdef DEBUG if (cnt == 0) { Serial.print(" res="); } Serial.print(rcvdata[cnt] , HEX); Serial.print(" "); #endif cnt++; if (cnt >= 128) { Serial.println("receive data over flow"); return FALSE; } } #ifdef DEBUG Serial.println(""); #endif if (rcvdata[0] == uni_res[0] && rcvdata[1] == uni_res[1] && rcvdata[2] == uni_res[2] && rcvdata[3] == uni_res[3]) { // RESPONSE/NORTIFICATION switch (rcvdata[4] << 8 | rcvdata[5]) { case (RES_B_ROOT): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("B-ROOT認証設定 Success"); #endif } else { Serial.println("B-ROOT認証設定 Error"); return FALSE; } case (NORT_WAKE): #ifdef DEBUG Serial.println("起動完了通知受信"); #endif break; case (RES_INI): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("初期化 成功"); #endif } else { #ifdef DEBUG Serial.println("初期化 失敗"); #endif return FALSE; } break; case (RES_PANA_SET): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("PANA Password 設定 成功"); #endif } else { #ifdef DEBUG Serial.println("PANA Password 設定 失敗"); #endif return FALSE; } break; case (CMD_B_ROOT_INI): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("Bルート情報認証設定 完了"); #endif } else { #ifdef DEBUG Serial.println("Bルート情報認証設定 失敗"); #endif return FALSE; } break; case (RES_SCAN): break; case (NORT_SCAN): if (rcvdata[12] == 0x00) { #ifdef DEBUG Serial.print(" スキャン応答有り★"); Serial.print(" ch[hex]:"); Serial.println(rcvdata[13], HEX); #endif //スキャンされたchを格納 scan_ch[0] = rcvdata[13]; #ifdef DEBUG Serial.print(" 応答台数:"); Serial.println(rcvdata[14]); Serial.print(" スマートメータMACアドレス[hex]:"); Serial.print(rcvdata[15], HEX); Serial.print(" "); Serial.print(rcvdata[16], HEX); Serial.print(" "); Serial.print(rcvdata[17], HEX); Serial.print(" "); Serial.print(rcvdata[18], HEX); Serial.print(" "); Serial.print(rcvdata[19], HEX); Serial.print(" "); Serial.print(rcvdata[20], HEX); Serial.print(" "); Serial.print(rcvdata[21], HEX); Serial.print(" "); Serial.println(rcvdata[22], HEX); #endif //スキャンされたMACアドレスを格納 pair_id[0] = rcvdata[15]; pair_id[1] = rcvdata[16]; pair_id[2] = rcvdata[17]; pair_id[3] = rcvdata[18]; pair_id[4] = rcvdata[19]; pair_id[5] = rcvdata[20]; pair_id[6] = rcvdata[21]; pair_id[7] = rcvdata[22]; #ifdef DEBUG Serial.print(" スマートメータPAN ID[hex]:"); Serial.print(rcvdata[23], HEX); Serial.print(" "); Serial.println(rcvdata[24], HEX); #endif //スキャンされたPANアドレスを格納 pan_id[0] = rcvdata[23]; pan_id[1] = rcvdata[24]; #ifdef DEBUG Serial.print(" 受信強度[dBm]:"); Serial.println((rcvdata[25] - 152) - 104, DEC); Serial.println ("スキャンに応答したデバイスが発見されたため接続を試みます"); #endif state = 4; //次のステータスへ移行 // Serial.println("ステータスを4に変更"); } else { #ifdef DEBUG Serial.print(" スキャン応答なし"); Serial.print(" ch[hex]:"); Serial.println(rcvdata[13], HEX); #endif } break; case (RES_B_ROOT_Active): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println(" Bルート動作開始 成功"); Serial.print(" 接続ch[hex]:"); Serial.println(rcvdata[13], HEX); Serial.print(" 接続PAN ID[hex]:"); Serial.print(rcvdata[14], HEX); Serial.print(" "); Serial.println(rcvdata[15], HEX); Serial.print(" 接続MACアドレス[hex]:"); Serial.print(rcvdata[16], HEX); Serial.print(" "); Serial.print(rcvdata[17], HEX); Serial.print(" "); Serial.print(rcvdata[18], HEX); Serial.print(" "); Serial.print(rcvdata[19], HEX); Serial.print(" "); Serial.print(rcvdata[20], HEX); Serial.print(" "); Serial.print(rcvdata[21], HEX); Serial.print(" "); Serial.print(rcvdata[22], HEX); Serial.print(" "); Serial.println(rcvdata[23], HEX); Serial.print(" 受信強度[dBm]:"); Serial.println((rcvdata[24] - 152) - 104, DEC); #endif return true; } else { Serial.println(" Bルート動作応答 失敗"); return false; } break; case (RES_B_ROOT_PANA): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("PANA開始応答 成功"); #endif } else { Serial.println("PANA開始応答 失敗"); return FALSE; } break; case (RES_HAN): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("Bルート応答 成功"); #endif } else { Serial.println("Bルート応答 失敗"); return FALSE; } break; case (NORT_PANA): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("PANA 認証成功"); Serial.print(" 認証先MACアドレス[hex]:"); Serial.print(rcvdata[13], HEX); Serial.print(" "); Serial.print(rcvdata[14], HEX); Serial.print(" "); Serial.print(rcvdata[15], HEX); Serial.print(" "); Serial.print(rcvdata[16], HEX); Serial.print(" "); Serial.print(rcvdata[17], HEX); Serial.print(" "); Serial.print(rcvdata[18], HEX); Serial.print(" "); Serial.print(rcvdata[19], HEX); Serial.print(" "); Serial.println(rcvdata[20], HEX); #endif } else { Serial.println("PANA 認証失敗"); return FALSE; } break; case (RES_PORTOPEN): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("UDP port open 成功"); #endif } else { Serial.println("UDP port open 失敗"); return FALSE; } break; case (RES_COM_STATUS): #ifdef DEBUG Serial.println(" "); Serial.print("スマートメータIPv6アドレス[hex]:"); #endif for (int i = 12; i < 28; i++) { #ifdef DEBUG Serial.print(rcvdata[i], HEX); Serial.print(" "); #endif IPv6_adr[i - 12] = rcvdata[i]; // IPv6アドレスを格納 } #ifdef DEBUG Serial.println(" "); Serial.print("送信元ポート番号[hex]:"); #endif for (int i = 28; i < 30; i++) { #ifdef DEBUG Serial.print(rcvdata[i], HEX); Serial.print(" "); #endif } #ifdef DEBUG Serial.println(" "); Serial.print("送信先ポート番号[hex]:"); #endif for (int i = 30; i < 32; i++) { #ifdef DEBUG Serial.print(rcvdata[i], HEX); Serial.print(" "); #endif } #ifdef DEBUG Serial.println(" "); Serial.print("送信元PAN ID[hex]:"); #endif for (int i = 32; i < 34; i++) { #ifdef DEBUG Serial.print(rcvdata[i], HEX); Serial.print(" "); #endif } #ifdef DEBUG Serial.println(" "); Serial.print("送信先アドレス種別[hex]:"); #endif for (int i = 34; i < 35; i++) { #ifdef DEBUG Serial.print(rcvdata[i], HEX); Serial.print(" "); #endif } #ifdef DEBUG Serial.println(" "); Serial.print("暗号化有無[hex]:"); #endif for (int i = 35; i < 36; i++) { #ifdef DEBUG Serial.print(rcvdata[i], HEX); Serial.print(" "); #endif } #ifdef DEBUG Serial.println(" "); Serial.print("受信データサイズ[Byte]:"); #endif for (int i = 36; i < 38; i++) { #ifdef DEBUG Serial.print(rcvdata[i], DEC); Serial.print(" "); #endif } #ifdef DEBUG Serial.println(" "); Serial.print("受信データ[hex]:"); #endif for (int i = 38; i < rcvdata[36] << 8 | rcvdata[37]; i++) { #ifdef DEBUG Serial.print(rcvdata[i], HEX); Serial.print(" "); #endif if (i > 50) { #ifdef DEBUG Serial.println(""); Serial.print("桁数が50桁を超えたので高速化のため表示をキャンセルします"); #endif i = rcvdata[36] << 8 | rcvdata[37] ; } } #ifdef DEBUG Serial.println(" "); #endif //■■■受信データをフォーマットに従って分解して表示する■■■ if (state == 8) { #ifdef DEBUG //スマートメータ―応答値の定義を確認する。 //動作状態、係数、積算電力量有効桁数、積算電力量単位 //動作状態 Serial.print("■動作状態[hex] (0x30 でON状態):"); Serial.println(rcvdata[53], HEX); //係数 Serial.print("■係数:"); Serial.println(rcvdata[56] << 8 * 3 | rcvdata[57] << 8 * 2 | rcvdata[58] << 8 * 1 | rcvdata[59]); //積算電力量有効桁数 Serial.print("■積算電力量有効桁数:"); Serial.println(rcvdata[62]); //積算電力量単位 Serial.print("■積算電力量単位(0x00:1kWh , 0x01:0.1kWh, ...:"); Serial.println(rcvdata[65], HEX); #endif } else if (state == 9) { //■■■計測時刻付きで積算電力計測値を取得する(30分ごと更新値)■■■ //■■■瞬時電力値、瞬時電流値を取得する■■■ #ifdef DEBUG //瞬時電力[W] : Serial.print("瞬時電力[W] : "); Serial.println(rcvdata[53] << 8 * 3 | rcvdata[54] << 8 * 2 | rcvdata[55] << 8 * 1 | rcvdata[56]); //瞬時電流[A] R相 : Serial.print("瞬時電流[A] R相 : "); Serial.println( (rcvdata[59] << 8 * 1 | rcvdata[60]) / 10); //瞬時電流[A] T相 : Serial.print("瞬時電流[A] T相 : "); Serial.println( (rcvdata[61] << 8 * 1 | rcvdata[62]) / 10); //積算電力計測値[kW] : Serial.print("計測日時:   "); Serial.println( rcvdata[65] << 8 * 1 | rcvdata[66]); Serial.print("年 "); Serial.print( rcvdata[67] ); Serial.print("月 "); Serial.print( rcvdata[68]); Serial.print("日 "); Serial.print( rcvdata[69] ); Serial.print("時 "); Serial.print( rcvdata[70]); Serial.println("分"); Serial.print("積算電力計測値[kW] : "); Serial.println((rcvdata[71] << 8 * 4 | rcvdata[72] << 8 * 3 | rcvdata[73] << 8 * 2 | rcvdata[74] << 8 * 1 | rcvdata[75]) * 10); #endif //変数定義確認用としてメモ //float Watt =0 ; //瞬時電力値 [W] //float Watt_th = 600; // 瞬時電力値からEV充電可否判断のための閾値 [W] Watt = float(rcvdata[53] << 8 * 3 | rcvdata[54] << 8 * 2 | rcvdata[55] << 8 * 1 | rcvdata[56]); Serial.println(Watt, 1);//シリアルプロッタ用に瞬時値のみをアウトプット if (Watt < Watt_th ) { //瞬時電力値が判定値を超えた場合にサーボを動かして充電プラグを外す start_time = millis(); while (start_time + change_time > millis()) { s_servo.write(map(millis(), start_time, start_time + change_time, 80, 170)); //0度から90度までchange_time[ms]かけて遷移させる。 } } else { start_time = millis(); while (start_time + change_time > millis()) { s_servo.write(map(millis(), start_time, start_time + change_time, 170, 80)); //0度から90度までchange_time[ms]かけて遷移させる。 } } // } else { //何もしない } break; case (RES_UDPSEND): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("UDP send 送信 成功"); #endif } else { Serial.println("UDP send 送信 失敗"); return FALSE; } break; case (0x2FFF): Serial.println("checksum error"); return FALSE; break; default: Serial.println("uni code error"); return FALSE; break; } } else { Serial.println("recv data error"); return FALSE; } return TRUE; } /******************************************************************************** Name : cmd_send Function : REQUEST command to bp35c0-j11 input : cmd - REQUEST command return : TRUE/FALSE *********************************************************************************/ boolean BP35C0J11::cmd_send(unsigned short cmd) { unsigned short hdr_chksum = uni_req[0] + uni_req[1] + uni_req[2] + uni_req[3] ; unsigned short dat_chksum = 0 ; unsigned short msg_length = 0 ; unsigned short dat_length = 0 ; unsigned short send_dat_size = 0 ; unsigned char data[128] = {0}; unsigned char send_data[128] = {0} ; unsigned char cnt = 0 ; switch (cmd) { case (CMD_RESET): dat_length = 0; msg_length = (unsigned short)(4 + dat_length); hdr_chksum += CMD_RESET + msg_length; dat_chksum = 0 ; msg_create(CMD_RESET , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, (msg_length + CMD_HDR_LEN)); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif // Serial.println("DEBUG 1-1"); break; case (CMD_INI): dat_length = (unsigned short)4; msg_length = (unsigned short )( 4 + dat_length); hdr_chksum += CMD_INI + msg_length ; for (cnt = 0 ; cnt < dat_length ; cnt++ ) { data[cnt] = ini_data[cnt] ; } for (cnt = 0 ; cnt < dat_length ; cnt++) { dat_chksum += data[cnt]; } msg_create(CMD_INI , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, (msg_length + CMD_HDR_LEN)); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif break; case (CMD_INI2): //スキャン完了後のBルート動作開始 dat_length = (unsigned short)4; msg_length = (unsigned short )( 4 + dat_length); hdr_chksum += CMD_INI + msg_length ; for (cnt = 0 ; cnt < dat_length ; cnt++ ) { data[cnt] = ini_data[cnt] ; if (cnt == 2) { data[cnt] = scan_ch[0] ; //アクティブスキャンの際に応答のあったチャンネルを設定する } } for (cnt = 0 ; cnt < dat_length ; cnt++) { dat_chksum += data[cnt]; } msg_create(CMD_INI , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, (msg_length + CMD_HDR_LEN)); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif break; case (CMD_B_ROOT_ACTIVE): //Bルート動作開始処理 dat_length = (unsigned short)0 ; msg_length = (unsigned short)(4 + dat_length); hdr_chksum += CMD_B_ROOT_ACTIVE + msg_length; msg_create(CMD_B_ROOT_ACTIVE , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, (msg_length + CMD_HDR_LEN)); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif break; case (CMD_B_ROOT_PANA): //PANA認証開始要求 dat_length = (unsigned short)0 ; msg_length = (unsigned short)(4 + dat_length); hdr_chksum += CMD_B_ROOT_PANA + msg_length; msg_create(CMD_B_ROOT_PANA , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, (msg_length + CMD_HDR_LEN)); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif break; case (CMD_PANA_SET): dat_length = (unsigned short)16 ; msg_length = (unsigned short)(4 + dat_length); hdr_chksum += CMD_PANA_SET + msg_length; for (cnt = 0 ; cnt < dat_length ; cnt++ ) { data[cnt] = password[cnt] ; } for (cnt = 0 ; cnt < dat_length ; cnt++) { dat_chksum += data[cnt]; } msg_create(CMD_PANA_SET , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, (msg_length + CMD_HDR_LEN)); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif break; //Bルート認証設定のコマンド列生成を追加 ここから case (CMD_B_ROOT_INI): dat_length = (unsigned short)(32 + 12) ; msg_length = (unsigned short)(4 + dat_length); hdr_chksum += CMD_B_ROOT_INI + msg_length; for (cnt = 0 ; cnt < dat_length ; cnt++ ) { if (cnt < 32) { data[cnt] = b_root_id[cnt] ; } else { data[cnt] = password[cnt - 32] ; } } for (cnt = 0 ; cnt < dat_length ; cnt++) { dat_chksum += data[cnt]; } msg_create(CMD_B_ROOT_INI , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, (msg_length + CMD_HDR_LEN)); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif break; //Bルート認証設定のコマンド列生成を追加 ここまで //Bルートのアクティブスキャンを実施 ここから case (CMD_SCAN): dat_length = (unsigned short)14 ; msg_length = (unsigned short)(4 + dat_length); hdr_chksum += CMD_SCAN + msg_length; for (cnt = 0 ; cnt < dat_length ; cnt++ ) { if ( cnt < 6) { data[cnt] = scan_CMD[cnt] ; } else { data[cnt] = b_root_id[cnt - 6 + 24] ; } } for (cnt = 0 ; cnt < dat_length ; cnt++) { dat_chksum += data[cnt]; } msg_create(CMD_SCAN , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, (msg_length + CMD_HDR_LEN)); #ifdef DEBUG Serial.print("send="); debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif // Serial.println ("Active scanは通過"); break; //Bルートのアクティブスキャンを実施 ここまで case (CMD_PORTOPEN): dat_length = 2; msg_length = (unsigned short)(4 + dat_length); hdr_chksum += CMD_PORTOPEN + msg_length; for (cnt = 0 ; cnt < dat_length ; cnt++ ) { data[cnt] = dist_port[cnt] ; } for (cnt = 0 ; cnt < dat_length ; cnt++) { dat_chksum += data[cnt]; } msg_create(CMD_PORTOPEN , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, msg_length + CMD_HDR_LEN); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif break; case (CMD_UDPSEND): if (state == 8) { //■■■スマートメータ―応答値の定義を確認する。■■■ //動作状態、係数、積算電力量有効桁数、積算電力量単位 send_dat_size = 20 ; //■下に記載されている送信データサイズと同じ値(DEC)■可変■ dat_length = 22 + send_dat_size ; msg_length = (unsigned short)(4 + dat_length); hdr_chksum += CMD_UDPSEND + msg_length; for (int i = 0 ; i < 16 ; i++ ) {//送信先のIPv6アドレス 16byte data[i] = IPv6_adr[i] ; } for (int i = 16 ; i < 18 ; i++ ) {//送信元のUDPポート番号 2byte data[i] = my_port[i - 16] ; } for (int i = 18 ; i < 20 ; i++ ) {//送信先のUDPポート番号 2byte data[i] = dist_port[i - 18] ; } //送信データサイズ 2byte  data[20] = (unsigned char)(send_dat_size >> 8); data[21] = (unsigned char)(send_dat_size & 0xFF); // send data length //send_dat_size のByte数として数えるのははここから //送信データヘッダ部 10 Byte  //ECHONET Lite 電文ヘッダ (2Byte) 基本固定値 data[22] = 0x10; data[23] = 0x81; //トランザクションID (2 Byte) 基本固定値 data[24] = 0x00; data[25] = 0x06; //送信元オブジェクト (3 Byte) 基本固定値 data[26] = 0x05; data[27] = 0xFF; data[28] = 0x01; //相手先オブジェクト (3 Byte) 基本固定値 data[29] = 0x02; data[30] = 0x88; data[31] = 0x01; //各種コマンド送信 10 Byte data[32] = 0x62; //サービスコード ESV 0x62=GET data[33] = 0x04; //処理プロパティ数 OPC ■■可変■■ ※EPC+PDCの組み合わせが何回繰り返されるか data[34] = 0x80; //ECHONET プロパティ EPC  0x80 : 動作状態 data[35] = 0x00; //プロパティデータカウンタ PDC data[36] = 0xD3; //ECHONET プロパティ 0xD3 :係数 data[37] = 0x00; //プロパティデータカウンタ PDC data[38] = 0xD7; //ECHONET プロパティ EPC  0xD7 : 積算電力量有効桁数 data[39] = 0x00; //プロパティデータカウンタ PDC data[40] = 0xE1; //ECHONET プロパティ 0xE1 :積算電力量単位 data[41] = 0x00; //プロパティデータカウンタ PDC //send_dat_size のByte数として数えるのははここまで for (cnt = 0 ; cnt < dat_length ; cnt++) { dat_chksum += data[cnt]; } msg_create(CMD_UDPSEND , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, msg_length + CMD_HDR_LEN); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif } else if (state == 9) { //■■■計測時刻付きで積算電力計測値を取得する(30分ごと更新値)■■■ //■■■瞬時電力値、瞬時電流値を取得する■■■ send_dat_size = 20 ; //■下に記載されている送信データサイズと同じ値(DEC)■可変■ dat_length = 22 + send_dat_size ; msg_length = (unsigned short)(4 + dat_length); hdr_chksum += CMD_UDPSEND + msg_length; for (int i = 0 ; i < 16 ; i++ ) {//送信先のIPv6アドレス 16byte data[i] = IPv6_adr[i] ; } for (int i = 16 ; i < 18 ; i++ ) {//送信元のUDPポート番号 2byte data[i] = my_port[i - 16] ; } for (int i = 18 ; i < 20 ; i++ ) {//送信先のUDPポート番号 2byte data[i] = dist_port[i - 18] ; } //送信データサイズ 2byte  data[20] = (unsigned char)(send_dat_size >> 8); data[21] = (unsigned char)(send_dat_size & 0xFF); // send data length //send_dat_size のByte数として数えるのははここから //送信データヘッダ部 10 Byte  //ECHONET Lite 電文ヘッダ (2Byte) 基本固定値 data[22] = 0x10; data[23] = 0x81; //トランザクションID (2 Byte) 基本固定値 data[24] = 0x00; data[25] = 0x06; //送信元オブジェクト (3 Byte) 基本固定値 data[26] = 0x05; data[27] = 0xFF; data[28] = 0x01; //相手先オブジェクト (3 Byte) 基本固定値 data[29] = 0x02; data[30] = 0x88; data[31] = 0x01; //各種コマンド送信 10 Byte data[32] = 0x62; //サービスコード ESV 0x62=GET data[33] = 0x04; //処理プロパティ数 OPC ■■可変■■ ※EPC+PDCの組み合わせが何回繰り返されるか data[34] = 0xE7; //ECHONET プロパティ 0xE7 :瞬時電力計測値 data[35] = 0x00; //プロパティデータカウンタ PDC data[36] = 0xE8; //ECHONET プロパティ 0xE8 :瞬時電流計測値 data[37] = 0x00; //プロパティデータカウンタ PDC data[38] = 0xEA; //ECHONET プロパティ EPC  0xEA : 定時積算電力量計測値 正方向 data[39] = 0x00; //プロパティデータカウンタ PDC data[40] = 0xEB; //ECHONET プロパティ 0xEB :定時積算電力量計測値 逆方向 data[41] = 0x00; //プロパティデータカウンタ PDC //send_dat_size のByte数として数えるのははここまで for (cnt = 0 ; cnt < dat_length ; cnt++) { dat_chksum += data[cnt]; } msg_create(CMD_UDPSEND , msg_length , hdr_chksum , dat_chksum, data , send_data ); Serial2.write(send_data, msg_length + CMD_HDR_LEN); #ifdef DEBUG debugmsg( msg_length + CMD_HDR_LEN , send_data); #endif } break; default: break; } return TRUE; } /******************************************************************************** Name : msg_create Function : create Request command format input : cmd - Request command msg_length - message data length hdr_chksum - header checksum dat_chksum - data checksum pdada - wireless data psend_data- request command format data return : - *********************************************************************************/ void static BP35C0J11::msg_create(unsigned short cmd , unsigned short msg_length , unsigned short hdr_chksum , unsigned short dat_chksum, unsigned char *pdata , unsigned char *psend_data ) { unsigned char cnt = 0 ; for (cnt = 0 ; cnt < 4 ; cnt++) { psend_data[cnt] = uni_req[cnt]; } psend_data[4] = (unsigned char)((cmd & 0xFF00) >> 8); psend_data[5] = (unsigned char)(cmd & 0xFF); psend_data[6] = (unsigned char)((msg_length & 0xFF00) >> 8); psend_data[7] = (unsigned char)(msg_length & 0xFF); psend_data[8] = (unsigned char)((hdr_chksum & 0xFF00) >> 8); psend_data[9] = (unsigned char)(hdr_chksum & 0xFF); psend_data[10] = (unsigned char)((dat_chksum & 0xFF00) >> 8); psend_data[11] = (unsigned char)(dat_chksum & 0xFF); if (msg_length > 4) { for (cnt = 0 ; cnt < msg_length - 4 ; cnt++) { psend_data[12 + cnt] = pdata[cnt]; } } } /******************************************************************************** Name : debugmsg Function : output serial console for debug input : datalength - output data lengh psend_data - output data pointer return : - *********************************************************************************/ void BP35C0J11::debugmsg(unsigned short datalength , unsigned char* psend_data) { unsigned char cnt = 0 ; for ( cnt = 0 ; cnt < datalength ; cnt++) { Serial.print(psend_data[cnt] , HEX); Serial.print(" "); } Serial.println(""); } ``` bp35c0-j11.h --- ``` arduino : bp35c0-j11.h /* bp35c0-j11.h Copyright (c) 2019 ROHM Co.,Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ extern unsigned char state; #include <Servo.h> static Servo s_servo; /**< Servo object */ #ifndef _BP35C0J11_H_ #define _BP35C0J11_H_ //#define DEBUG #define CMD_HDR_LEN ((unsigned char)8) #define UNI_CODE_LEN ((unsigned char)4) #define CMD_RESET (0x00D9) #define CMD_INI (0x005F) #define CMD_HAN (0x000A) #define CMD_PANA (0x003A) #define CMD_PANA_SET (0x002C) #define CMD_CON_SET (0x0025) #define CMD_UDPSEND (0x0008) #define CMD_SCAN (0x0051) #define CMD_PORTOPEN (0x0005) #define NORT_WAKE (0x6019) #define RES_INI (0x205F) #define RES_HAN (0x200A) #define RES_PANA (0x203A) #define RES_PANA_SET (0x202C) #define RES_CON_SET (0x2025) #define RES_UDPSEND (0x2008) #define RES_SCAN (0x2051) #define NORT_SCAN (0x4051) #define RES_PORTOPEN (0x2005) #define NORT_PANA (0x6028) #define RES_B_ROOT (0x2054) // Bルート認証情報設定の応答コマンド #define RES_B_ROOT_Active (0x2053) //Bルート動作開始応答 #define RES_B_ROOT_PANA (0x2056) //PANA認証開始応答 #define RES_COM_STATUS (0x6018) //接続状態変更通知応答 #define CMD_INI2 (0x1111) // 状態遷移用のダミー #define CMD_B_ROOT_INI (0x0054) // Bルート認証情報設定 #define CMD_B_ROOT_ACTIVE (0x0053) //Bルート動作開始命令 #define CMD_B_ROOT_PANA (0x0056) // PANA認証開始要求 #define TIMEOUT ((unsigned short)90000) #define PIN_ENABLE (PIN_D20) // level shifter enable pin #define PIN_RESET (PIN_D21) // wisun module reset pin #define TRUE 1 #define FALSE 0 typedef struct { unsigned char uni_code[4]; unsigned char cmd_code[2]; unsigned char msg_len[2]; unsigned char hdr_chksum[2]; unsigned char dat_chksum[2]; unsigned char data[128]; }CMD_FORMAT; class BP35C0J11 { public: BP35C0J11(void); void j11_init(void); boolean wait_msg(void); boolean cmd_send(unsigned short cmd); void static msg_create(unsigned short cmd , unsigned short msg_length ,unsigned short hdr_chksum , unsigned short dat_chksum, unsigned char *pdata , unsigned char *psend_data ); private: void static debugmsg(unsigned short datalength , unsigned char *psend_data); }; #endif //_BP35C0J11_H_ ```