Arduinoで家の消費電力を計測する
以前、Raspberry pi Zero Wを使って家の電力を計測した記事を掲載した。
ラズパイはメモリ容量やCPU性能のスペックも高いのでネットワークコーディングとかは難なくこなせる一方、OS上で動くため長時間動かし続けるにはネイティブに近いAruduinoには劣ってしまう。事実、ADCのエラーでちょいちょい落ちているようで原因が我が糞コードのメモリリークなのか、OS依存なのか、ライブラリなのかよくわからない・・・。結局プロセスを監視して落ちたら再起動という苦肉の策で回避するしかない。
センシングとサーバーへのデータ送信程度ならば、Aruduino系のマイコンで、とりわけwifi搭載のマイコンの代表であるESP系で処理したほうが安定性がいい。電気を供給すれば勝手にコードが走るので使い勝手もイイ。というわけで、今回はArduinoNANO + ESP8266でのセンシングを行ってみました。
え?ESP8266だけでいいじゃん!!
ですよね~w。こうなった理由も書いときます。マイコンをネットで勉強し始めて、右も左もわからないときです。Arduino NANOに接続するWiFiモジュールという視点で探していました。そこでESP8266を見つけたわけです。今となれば、もーちょい深読みすれば、ESP8266に直接スケッチを書き込んでゴニョればいいやと思うけど、Wifiモジュール付きのマイコンだとは思わずに・・・
ESP8266はWifiモジュールなんだ!Arduinoとつなげられる!スゴイ!
と思い込んでしまい、今回はArduino+ESP8266でのWifi接続を必死でやってしまいました!更にADCやら、レギュレータやらつけたらゴチャゴチャ構成になってしまったわけです。でも、このことが無駄にならないよう書き留めときます(´∀` )。ちなみに、今度ESP8266のみで行う気温・湿度・気圧・PM2.5のセンシングもやったので記事にしてみます。
[2021-08-15] 追記
ESP8266のみで組み立てました。こっちの方がすっきりとわかりやすいと思います。でもphpなどはこの記事に書いてあるので、踏まえたうえで読んでもらえばと思います
というわけで、早速準備をしていきます。
データベースサーバーの確保
センシングしたデータを保存する場所を決めておかなきゃならないです。とりあえず、PHPが実行できてデータベースサーバーが動かせればいいなぁという感じ。たまたま家ではバッキャローのLinkStationにwebサーバー機能とMySQLサーバー機能があったのでそれを使うことにしました。レンタルサーバーでもいいけども、センシングの頻度によりサーバーに負荷がかかりやすいので注意が必要かなとおもう。基本はLAN内にサーバーを置いて運用するほうがいいけども、大がかりになるようであれば、大人しくラズパイで運用して、ローカルストレージに保存していった方が現実的です。それか、ラズパイをSQLサーバーにするかですね。
MySQLサーバーを立てて、phpMyAdminを開きます。homepowerデータベースを作成し、その中にpower_arduinoテーブルを作ります。構造は以下の通りです。
Arduinoから直接MySQLにアクセスするライブラリを使おうかなと思いましたが、GETリクエストを送ってPHPでのPDO経由のほうがMySQLでもSQLiteでも汎用性が効くので、こっちを採用しました。データをセットするPHPは
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<?php $ch01_power = $_GET['ch01_power']; $ch23_power = $_GET['ch23_power']; $ch01_watthour = $_GET['ch01_watthour']; $ch23_watthour = $_GET['ch23_watthour']; $now_t = new DateTime(); $date_st = $now_t->format('Y-m-d'); $time_st = $now_t->format('H:i:s'); try { $pdo = new PDO('mysql:host=localhost;dbname=homepower; port=3306; charset=utf8;', 'ユーザー名', 'パスワード'); echo "接続成功\n"; } catch (PDOException $e) { echo "接続失敗: " . $e->getMessage() . "\n"; exit(); } $sql = 'INSERT INTO power_arduino (date_st,time_st,ch01_power,ch23_power,ch01_watthour,ch23_watthour) VALUE (:date_st,:time_st,:ch01_power,:ch23_power,:ch01_watthour,:ch23_watthour)'; $prepare = $pdo->prepare($sql); $prepare->bindValue(':date_st', $date_st, PDO::PARAM_STR); $prepare->bindValue(':time_st', $time_st, PDO::PARAM_STR); $prepare->bindValue(':ch01_power',$ch01_power, PDO::PARAM_STR); $prepare->bindValue(':ch23_power',$ch23_power, PDO::PARAM_STR); $prepare->bindValue(':ch01_watthour',$ch01_watthour, PDO::PARAM_STR); $prepare->bindValue(':ch23_watthour',$ch23_watthour, PDO::PARAM_STR); $prepare->execute(); $pdo = null; $prepare = null; ?> |
PDO接続の詳細は他のサイトなどで確認してください。ちなみにWebサーバーとMySQLサーバーは同一なのでアドレスはlocalhostとなります。ポートも適宜設定してください。あとはpower_arduinoに書き込む日時、GETパラメーターで渡されたチャンネル0-1間と2-3間の消費電力(power)と直前のデータ送信からの消費電力量(watthour)をテーブルに記録していくだけです。
スケッチと組み立て
使うもの一覧
- Arduino nano (の互換品)
- ESP8266 (ロジックレベルは3.3Vです)
- ADS1115(2チャンネル分の差動モードがありSCR-013-030を2個と相性がいい)
- SCR-013-030×2個(クランプメーター30A)
- ロジックレベルコンバータ(3.3V<->5Vのロジックレベル変換)
- マイクロUSBコネクタ(5V供給)
- 3.3vレギュレーター(5V->3.3V供給[ESP8266用])
- ケースとして100均のタッパ
- 100均ステレオイヤホンジャックの延長コード(クランプメーター接続用)
- 100均マイクロUSB充電ケーブル2A対応品(給電用)
上の回路図TxとRx、SCLとSDA・・・あってると思うけど。思い出しながらざっくり書いたので・・・。後で調べて変だったら直します。
ちなみに3.3VはArduino nanoからも取れますが、電流量が圧倒的に足りないのでレギュレーターを使いましょう。ADCは5VでESP8266-NANO間はロジック変換を使います。再度しつこいようですがESP8266のみでイくならnanoとロジックレベルコンバーターは不要ですw。
上のサイトを参考にESP8266をモデムのように使います。ソフトウエアとリアルを使ってESP8266にATコマンドを通るようにしておき理解を深めておきます。通信速度を9600bpsに落としておいたほうが安定するとのことなのでATコマンドを使ってあらかじめ設定しておきます。
そして、上のサイトのまんまでhttpリクエストを送るスケッチに電力測定をちょこちょこっと加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
#include <Wire.h> #include <Adafruit_ADS1015.h> #include "ESP8266.h" #include <SoftwareSerial.h> #include <avr/wdt.h> #define SSID "SSID" #define PASSWORD "password" #define HOST_NAME "192.168.10.20" //データベースサーバー #define HOST_PORT 81 //httpサーバーのポート SoftwareSerial mySerial(11, 10); ESP8266 wifi(mySerial); Adafruit_ADS1115 ads; const float FACTOR = 30; //30A/1V const float multiplier = 0.03125F * 1.27; float ch01_current,ch23_current; long loop_time = millis(); void setup(){ Serial.begin(9600); Serial.print("setup begin\r\n"); Serial.print("FW Version:"); Serial.println(wifi.getVersion().c_str()); if (wifi.setOprToStationSoftAP()) { Serial.print("to station + softap ok\r\n"); } else { Serial.print("to station + softap err\r\n"); } if (wifi.joinAP(SSID, PASSWORD)) { Serial.print("Join AP success\r\n"); Serial.print("IP:"); Serial.println( wifi.getLocalIP().c_str()); } else { Serial.print("Join AP failure\r\n"); } if (wifi.disableMUX()) { Serial.print("single ok\r\n"); } else { Serial.print("single err\r\n"); } Serial.print("setup end\r\n"); ads.setGain(GAIN_FOUR); ads.begin(); } void printMeasure(String prefix, float value, String postfix){ Serial.print(prefix); Serial.print(value,3); Serial.println(postfix); } void loop(){ wdt_enable(WDTO_8S); getRMS(); float ch01_power = ch01_current * 100; float ch23_power = ch23_current * 100; long pass_time = millis() - loop_time; loop_time = millis(); printMeasure("CH01_POWER: ",ch01_power,"W"); printMeasure("CH23_POWER: ",ch23_power,"W"); printMeasure("TIME: ",pass_time,"mSec"); float ch01_watthour = (ch01_power * pass_time) / 3600000; float ch23_watthour = (ch23_power * pass_time) / 3600000; char ch01_power_st[10]; char ch23_power_st[10]; char ch01_watthour_st[10]; char ch23_watthour_st[10]; dtostrf(ch01_power, 0, 6,ch01_power_st); dtostrf(ch23_power, 0, 6,ch23_power_st); dtostrf(ch01_watthour, 0, 6,ch01_watthour_st); dtostrf(ch23_watthour, 0, 6,ch23_watthour_st); char hello[190]; sprintf(hello, "GET /set_power.php?ch01_power=%s&ch23_power=%s&ch01_watthour=%s&ch23_watthour=%s HTTP/1.1\r\nHost:192.168.10.20\r\nConnection:close\r\n\r\n",ch01_power_st,ch23_power_st,ch01_watthour_st,ch23_watthour_st); Serial.println(hello); if (wifi.createTCP(HOST_NAME, HOST_PORT)) { Serial.print("create tcp ok\r\n"); wifi.send((const uint8_t*)hello, strlen(hello)); uint8_t buffer[32] = {0}; uint32_t len = wifi.recv(buffer, sizeof(buffer), 5000); if (len > 0) { Serial.print("Received:["); for(uint32_t i = 0; i < len; i++) { Serial.print((char)buffer[i]); } Serial.print("]\r\n"); } } else { Serial.print("create tcp err\r\n"); } wdt_reset(); wdt_disable(); } void getRMS(){ float voltage,voltage2; float corriente,corriente2; float sum = 0,sum2 = 0; long timepo = millis(); int counter = 0; while(millis() - timepo < 5000){ //ch0-1計測 voltage = ads.readADC_Differential_0_1() * multiplier; corriente = voltage * FACTOR; corriente /= 1000.0; sum += sq(corriente); //ch2-3計測 voltage2 = ads.readADC_Differential_2_3() * multiplier; corriente2 = voltage2 * FACTOR; corriente2 /= 1000.0; sum2 += sq(corriente2); counter = counter + 1; } corriente = sqrt(sum / counter); corriente2 = sqrt(sum2 / counter); ch01_current = corriente; ch23_current = corriente2; } |
getRMS()は以前ラズパイで紹介したものを、そのまま使っています。RMSの説明はそちらをどうぞ。
どうしてもネットワークの不具合が出た時のタイムアウト処理として、ウォッチドックタイム(番犬)でプロセスを監視し、時間内(上のスケッチでは8秒以内)に計測送信ができなければ再起動を行うようにしています。再起動後は直前の計測時間(loop_time)が欠測してしまうため、その間の消費電力が加算されない。欠測しても数秒程度なので問題はないと思う。ウォッチドックタイマーについては下のサイトを参考にしました。
とりあえず、ESP-WROOM-02の在庫がなくて、ESP-01でテストしてみました(オイ!)。テストですよ?テストです。
コンデンサはレギュレータの説明書の推奨利用の通りにつけています。ステレオジャックは2系統(通常の家の電力線が単相3線方式なので)とUSBは給電用です。スケッチ書き込みはNANOのMiniUSBから行います。ネジとピンやケーブルが接触しているように見えますが立体的に避けているので接触してません。GNDの引き回しは裏です。素人工作なので「半田がー」「色がー」とかはご勘弁を・・・。
分電盤への設置はラズパイの時と同じです。とりあえず、動かしてみました。
テーブルの中身です。5秒毎にチャンネル毎の電力と電力量が記録されてます。でもどのくらい正確なのかがわかりません。Looop電力を使ってるので、日別の利用電力量(スマートメータ値)をネットで確認できます。比較してみました
単位はワット時(Wh)です。パーセントは誤差です。大体、3パーセント程度の誤差が出ているようです。looop電気のサイト上では下二桁の値は出てないので正確な数値はわかりませんが、概ね良好に計測できているようです。
ダッシュボード作ったりIoTするのに本日の電力量を取得するPHPも貼っておきます。JSON形式で吐き出します。
この記事付近で使ってます
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
<?php header('content-type: application/json; charset=utf-8'); $now_t = new DateTime(); $date_st = $now_t->format('Y-m-d'); $time_st = $now_t->format('H:i:s'); try { $pdo = new PDO('mysql:host=localhost;dbname=homepower; port=3306; charset=utf8;', 'ユーザー名', 'パスワード'); //echo "接続成功\n"; } catch (PDOException $e) { echo "接続失敗: " . $e->getMessage() . "\n"; exit(); } $result = array(); //直近(現在)の消費電力取得 $sql = 'SELECT * FROM (SELECT * FROM power_arduino WHERE `date_st`=:date_st) as TODAY ORDER BY `time_st` desc LIMIT 1;'; $prepare = $pdo->prepare($sql); $prepare->bindValue(':date_st', $date_st, PDO::PARAM_STR); $prepare->execute(); $now_p = $prepare->fetchAll(PDO::FETCH_ASSOC); $result['now_power'] = $now_p[0]; //今日の積算消費電力を取得 $sql = 'select sum(`ch01_watthour`)+sum(`ch23_watthour`) as today_power from power_arduino where `date_st`=:date_st'; $prepare = $pdo->prepare($sql); $prepare->bindValue(':date_st', $date_st, PDO::PARAM_STR); $prepare->execute(); $today_power = $prepare->fetchAll(PDO::FETCH_ASSOC); $result['today_power'] = $today_power[0]['today_power']; $json = json_encode($result); // 結果を出力 echo($json); $pdo = null; $prepare = null; ?> |