miso が 2024年01月25日22時11分10秒 に編集
コメント無し
本文の変更
# 家庭での電力消費、見える化の魔法 ## 電力消費 目に見えないミステリー 省エネを始めるには家電がどれだけの電力を消費しているかを知ることが最初のステップです。ただし、普段の生活でこの電力消費を確認するのは非常に難しい課題で簡単に確認することが出来ません。 重量課金だというのに、なんと恐ろしい事でしょうか。 ![キャプションを入力できます](https://camo.elchika.com/725de438e396d13c27af7338424faabfae560c95/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32646132616164352d373663382d343562642d393434382d3262306431663736653138352f39653766656262322d646166332d346636322d623132302d646565393137383932623164/) ## Sonyの秘密兵器SpresenseとROHMのWi-SUNボードの登場 ここで、Sonyの秘密兵器であるマイコンボード「Spresense」と無線通信規格「Wi-SUN」が活躍します。これらを駆使し、自宅の電力消費を「見える化」するシステムを構築しました。 ありがたくも、コンテスト開催中でモニターとしてこれらの秘密兵器を受け取ることができました。 お陰でNature Remo E liteを買うお金が浮きました!!貧乏なので本当にありがたいです!ありがたや!ありがたや! ![キャプションを入力できます](https://camo.elchika.com/8267b1c8491099ae37260319a110ff97d8c58263/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32646132616164352d373663382d343562642d393434382d3262306431663736653138352f66333139306564622d636365392d346334342d613061642d373837306665633835333236/) ## 結論というか結果、見える化と省エネに貢献 スマートメーター→Wi-SUN→Spresense→PCの流れでデータを処理し、最終的にProcessingで値をグラフとしてプロットしつつ、過去10件の値を文字列で表示してみました。リアルタイムで更新されるので、家電をOFFした瞬間に消費電力が下がるので何の家電がどれだけの消費電力であったかを簡単に把握することが出来ます!!この画面を常時表示することで子供達も積極的に省エネに協力してくれました、すばらしい!! ![キャプションを入力できます](https://camo.elchika.com/c01aa82d560cc70efc2758a5402a532e97b90a4c/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32646132616164352d373663382d343562642d393434382d3262306431663736653138352f63643566383934662d643263342d343239362d613732332d343461663965333566346438/) こんな感じで時間の経過と共に消費電力がグラフ化され、良い感じに可視化されてく。 ![キャプションを入力できます](https://camo.elchika.com/c1c9e66b7af3925b4a6f3c73cf6d76ad0f284911/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32646132616164352d373663382d343562642d393434382d3262306431663736653138352f36616461663061662d346634302d346466332d393539322d626431666331346334636530/) ## ハードウェア構成 簡単です。Spresense メインボードにSPRESENSE-WiSUN-EVK-701を接続するだけのお手軽構成。13250円、お手頃価格ですね。 ### 部品構成 | 品名 | 価格 | URL | |------|------|------| | SPRESENSEメインボード[CXD5602PWBMAIN1] | ¥6,050 | [商品リンク](https://ssci.to/3900) | | SPRESENSE-WISUN-EVK-701 | ¥7,200 | [商品リンク](https://www.zaikostore.com/zaikostore/stockDetail?productIdOfHitotsukara=pr14922577) | ### 外観 ![キャプションを入力できます](https://camo.elchika.com/b5f1d7fb0e9c1e7c694b8e6dcc00f904c183c215/687474703a2f2f73746f726167652e676f6f676c65617069732e636f6d2f656c6368696b612f76312f757365722f32646132616164352d373663382d343562642d393434382d3262306431663736653138352f33356132333733352d613734342d343665362d396138612d663865326565383961363235/) ## ソースコード (Spresense) 偉大なる先達である、 [Spresenseで家庭の消費電力値をスマートメータから取得してEVの充電を制御するデバイス by 84mot \| elchika](https://elchika.com/article/98088034-a54e-4f3f-9231-2816bed9ca03/) を参考に改変を加えて作成しました。 ### spresense-wisun.ino ```cpp /* 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 WARRANyTIES 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; } // シリアルモニタ用 #ifdef DEBUG Serial.println("瞬時電力 [W]"); #endif 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; } else { state = 1; } 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 } 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 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 state = 7; } else { #ifdef DEBUG Serial.println("CMD_PORTOPENの送信に失敗しました"); #endif state = 0; } 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 state = 8; } else { #ifdef DEBUG Serial.println("PANA認証開始応答を確認できませんでした。再起動します。"); #endif 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 rc = bp35c0j11.wait_msg(); if (rc == TRUE) { #ifdef DEBUG Serial.println("statusを9に変更"); #endif state = 9; } else { #ifdef DEBUG Serial.println("メーターからの応答がタイムアウトしたため再起動します"); #endif state = 0; } } 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 rc = bp35c0j11.wait_msg(); if (rc == TRUE) { #ifdef DEBUG Serial.println("全シーケンス終了。再度計測します"); #endif state = 9; } else { #ifdef DEBUG Serial.println("メーターからの応答がタイムアウトしたため再要求を実施します"); #endif } } else { #ifdef DEBUG Serial.println("データ送信要求 失敗"); #endif } break; default: break; } } ``` ### bp35c0-j11.cpp ```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', '0', '0', '0', '0', '0', '9', '9', '0', '2', '1', '7', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', 'B', 'B', 'A', 'A', 'A', 'A'}; unsigned const char password[12] = {'N', 'X', 'F', 'Y', '2', '2', '3', '4', 'A', 'A', 'A', 'A'}; 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) { #ifdef DEBUG Serial.println("receive timeout -> hardware reest"); #endif 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) { #ifdef DEBUG Serial.println("receive data over flow"); #endif 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 { #ifdef DEBUG Serial.println("B-ROOT認証設定 Error"); #endif 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 { #ifdef DEBUG Serial.println(" Bルート動作応答 失敗"); #endif return FALSE; } break; case (RES_B_ROOT_PANA): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("PANA開始応答 成功"); #endif } else { #ifdef DEBUG Serial.println("PANA開始応答 失敗"); #endif return FALSE; } break; case (RES_HAN): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("Bルート応答 成功"); #endif } else { #ifdef DEBUG Serial.println("Bルート応答 失敗"); #endif 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 { #ifdef DEBUG Serial.println("PANA 認証失敗"); #endif return FALSE; } break; case (RES_PORTOPEN): if (rcvdata[12] == 0x01) { #ifdef DEBUG Serial.println("UDP port open 成功"); #endif } else { #ifdef DEBUG Serial.println("UDP port open 失敗"); #endif 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 { #ifdef DEBUG Serial.println("UDP send 送信 失敗"); #endif return FALSE; } break; case (0x2FFF): #ifdef DEBUG Serial.println("checksum error"); #endif return FALSE; break; default: #ifdef DEBUG Serial.println("uni code error"); #endif return FALSE; break; } } else { #ifdef DEBUG Serial.println("recv data error"); #endif 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 ```cpp /* 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_ ``` ## ソースコード (Processing) ```java import processing.serial.*; import java.text.SimpleDateFormat; import java.util.Date; Serial myPort; // シリアルポート String val; // ポートから読み込んだデータを格納する変数 float[] values; // 過去24時間のデータを格納する配列 String[] lastTenValues; // 最新の10個のデータ値 int dataIndex = 0; // int graphShiftX = 0; // グラフの描画開始位置を制御する変数 void setup() { fullScreen(); // フルスクリーン表示 println(Serial.list()); // 利用可能なシリアルポートをリストアップ myPort = new Serial(this, Serial.list()[0], 115200); // COM6ポートを開く、ボーレートを115200に設定 myPort.bufferUntil('\n'); // 改行コードが来るまでバッファリング values = new float[24 * 60]; // 24時間分のデータを格納する配列を初期化(1分ごとのデータと仮定) lastTenValues = new String[10]; // 最新の10個のデータ値を格納する配列を初期化 } void draw() { background(255); // 背景を白に設定 drawGraph(); // グラフを描画 displayLastTenValues(); // 最新の10個のデータ値を表示 } void serialEvent(Serial p) { val = p.readStringUntil('\n'); // 改行コードまでの文字列を読み込む if (val != null) { val = trim(val); // 余分な空白を削除 try { float currentValue = float(val); // 文字列をfloatに変換 values[dataIndex] = currentValue; // 配列に値を格納 updateLastTenValues(currentValue); // 最新の10個の値を更新 dataIndex = (dataIndex + 1) % values.length; // インデックスを更新 graphShiftX += 10; // グラフを右に10px移動 if (graphShiftX > width - 100) { graphShiftX = 0; // グラフが右端に達したら、左端から再開
// ここでvalues配列を初期化 for (int i = 0; i < values.length; i++) { values[i] = 0; } dataIndex = 0; // dataIndexもリセット
} } catch (Exception e) { println("Error converting data: " + val); } } } void drawGraph() { float graphTop = 50; // グラフ上部の余白 float graphBottom = height - 50; // グラフ下部の余白 float graphMaxValue = 5000; // 表示する最大電力値 stroke(50); noFill(); beginShape(); for (int i = 0; i < values.length; i++) { float x = map(i, 0, values.length - 1, 50, width - 50) + ( i * 10 ); // 縦軸位置をマッピング(値が0の場合はgraphBottom、graphMaxValueの場合はgraphTop) float y = map(values[i], 0, graphMaxValue, graphBottom, graphTop); vertex(x, y); } endShape(); drawScale(); // スケールを描画 } void drawScale() { // スケールの線の色を灰色に設定 stroke(225, 225, 225); float graphTop = 50; // グラフ上部の余白 float graphBottom = height - 50; // グラフ下部の余白 float graphMaxValue = 5000; // 表示する最大電力値 // 水平線と電力値テキストのための設定 stroke(150); textAlign(RIGHT, CENTER); // テキストを右揃えで中央揃えに for (int i = 0; i <= graphMaxValue; i += 500) { float y = map(i, 0, graphMaxValue, graphBottom, graphTop); // 縦軸位置をマッピング line(50, y, width - 50, y); // 水平線を描画 fill(0); // 文字色を黒に text(i + " Wh", 45, y); // 電力値テキストを描画(横軸から少し左側に設定) } // 垂直線と時間テキストのための設定 textAlign(CENTER, BOTTOM); // テキストを中央揃えで下揃えに for (int i = 0; i <= 24; i++) { float x = map(i, 0, 24, 50, width - 50); // 横軸位置をマッピング line(x, graphTop, x, graphBottom); // 垂直線を描画 } } void displayLastTenValues() { float startY = height / 10.0; // 開始位置を画面の高さの1/10に設定 int padding = 4; // テキストの周りの余白 int lineHeight = 20; // 1行の高さ for (int i = 0; i < lastTenValues.length; i++) { if (lastTenValues[i] != null) { float textWidth = textWidth(lastTenValues[i]); // テキストの幅を取得 fill(255); // 背景色を白に設定 // 四角形の左上角がテキストの左上に来るように調整 rect(130, startY + i * lineHeight, textWidth + (padding * 2), lineHeight); fill(0); // 文字の色を黒に設定 // テキストを表示(テキストの位置は四角形の中央に来るように調整) text(lastTenValues[i], 180 + padding, startY + 5 + i * lineHeight + (lineHeight / 2)); } } } float averageOfLastTenValues() { float sum = 0; int count = 0; for (int i = 0; i < lastTenValues.length; i++) { if (lastTenValues[i] != null) { String[] parts = split(lastTenValues[i], ' '); float value = float(parts[1]); sum += value; count++; } } return count == 0 ? 0 : sum / count; } void updateLastTenValues(float newValue) { float average = averageOfLastTenValues(); if (newValue > 3 * average && average != 0) { println("Ignored outlier value: " + newValue); return; } SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss,"); String timestamp = sdf.format(new Date()); String newValueStr = timestamp + " " + newValue + " Wh"; for (int i = lastTenValues.length - 1; i > 0; i--) { lastTenValues[i] = lastTenValues[i - 1]; // 値を1つずつシフト } lastTenValues[0] = newValueStr; // 最新の値を先頭に追加 } float maxValue(float[] arr) { float maxVal = arr[0]; for (int i = 1; i < arr.length; i++) { if (arr[i] > maxVal) { maxVal = arr[i]; } } return maxVal; } ``` ## さいごに モニターにてBLEも頂いていたのでBLE化も視野に入れていたのですが、業務的な都合で時間が取れず今回はここまでとしました。BLE化できれば常設で利用出来るので本格的に活用できたのですが・・・・残念!!