M5Stackでスマートメーターの電力量を取得してAmbientでグラフ表示

# はじめに
前回の記事[M5Stackでスマートメーターの瞬時電力をBP35A1から取得して表示してみた](https://qiita.com/KatsuShun89/items/7a09917bb813b42bb9d1)の続きです。

今回は瞬時電力量[W], 定時積算電力量計測値 をBP35A1経由で取得して、Ambientという簡単にデータをロギング出来るサービスでグラフ化してみます。

同時にBME280で取得した温度・湿度・気圧もAmbientにアップロードしています。
接続の様子はこのようになっています。
![50442327_258230331760431_4547688269822820352_n.jpg](https://qiita-image-store.s3.amazonaws.com/0/302503/5c4190b8-1bd0-21e9-0eb9-5737c8582da1.jpeg)

M5StackとBP35A1の接続方法などは前回記事を参照してください。

# ソースコード
ソースコードはこちらで公開しています。
因みに公開しているソースはBME280で取得した温度・湿度・気圧も同時にAmbientで保存しています。

https://github.com/Katsushun89/M5Stack_SmartMeter

# 瞬時電力量の取得
瞬時電力量の取得方法は前回記事を参照してください。
[M5Stackでスマートメーターの瞬時電力をBP35A1から取得して表示してみた](https://qiita.com/KatsuShun89/items/7a09917bb813b42bb9d1)

# 定時積算電力量計測値
定時積算電力量計測値とは

echonetの仕様書
https://echonet.jp/wp/wp-content/uploads/pdf/General/Standard/Release/Release_H_jp/Appendix_H.pdf
３．３．２５低圧スマート電力量メータクラス規定(P311)
の記載されています。

> 最新の30分毎の計測時刻における積算電力量(正方向計測値)を、計測年月日を4バイト、
計測時刻を3バイト、積算電力量（正方向計測値）4バイトで示す。
・計測年月日`YYYY:MM:DD`、計測時刻`hh:mm:ss` 、積算電力量10進表記で最大8桁

この値を取得するためには同時に
- 積算電力量単位（正方向、逆方向計測値）
- 係数
- 積算履歴収集日１
の取得が必要です。

BP35A1用のクラスを用意してありますので、そのメソッドgetIntegralPowerConsumption()をloop()で呼び出せば、積算電力量を取得出来ます。

少し長いですが、getIntegralPowerConsumption()のソースコードです。

``` BP35A1.h
    bool getIntegralPowerConsumption(integral_power_consumpution_t *integral_power){
        Serial.println(__func__);
        const uint32_t DATA_STR_LEN = 20;
        char data_str[DATA_STR_LEN] = {"0"};

        //EHD
        data_str[0] = char(0x10);
        data_str[1] = char(0x81);
        //TID
        data_str[2] = char(0x00);
        data_str[3] = char(0x01);
        //SEOJ
        data_str[4] = char(0x05);
        data_str[5] = char(0xff);
        data_str[6] = char(0x01);
        //DEOJ 低圧スマート電力量メータークラス
        data_str[7] = char(0x02);
        data_str[8] = char(0x88);
        data_str[9] = char(0x01);
        //ESV(0x62:プロパティ値読み出し)
        data_str[10] = char(0x62);

        //OPC(4個)
        data_str[11] = char(0x04);
     
        //EPC
        //係数:0xD3
        data_str[12] = char(0xd3);
        //PDC read:0
        data_str[13] = char(0x00);

        //EPC
        //積算電力量単位:0xE1
        data_str[14] = char(0xe1);
        //PDC read:0
        data_str[15] = char(0x00);

        //EPC
        //積算履歴収集日１:0xE5
        data_str[16] = char(0xe5);
        //PDC read:0
        data_str[17] = char(0x00);

        //EPC
        //定時積算電力量計測値(正方向計測値):0xEA
        data_str[18] = char(0xea);
        //PDC read:0
        data_str[19] = char(0x00);

        String data_str_len = String(DATA_STR_LEN, HEX);
        data_str_len.toUpperCase();
        uint32_t str_len = data_str_len.length(); 
        for(uint32_t i = 0; i < 4 - str_len; i++){
            data_str_len = "0" + data_str_len;
        }
        
        String com_str = "SKSENDTO 1 " + ipv6_addr_ + " 0E1A 1 " + data_str_len + " ";
        byte com_bytes[1024];
        com_str.getBytes(com_bytes, com_str.length() + 1);
        for(uint32_t i = 0; i < DATA_STR_LEN; i++){
            com_bytes[com_str.length() + i] = data_str[i];
        }
        
        uint32_t loop_cnt = 0;
        do{
            String measure_value;
            int32_t start_data; 
            int32_t end_data; 
            String hex_PDC;
            uint32_t int_PDC;

            Serial2.write(com_bytes, com_str.length() + DATA_STR_LEN);
            Serial.write(com_bytes, com_str.length() + DATA_STR_LEN);
            Serial2.println();
            String expected_res = "1081000102880105FF01";
            bool is_received_res = waitExpectedRes(WAIT_MEASURE_TIME, expected_res, &measure_value);
            if(!is_received_res){
               Serial.println("measure nores err");
               loop_cnt++;
               continue;
            }

            uint32_t offset = measure_value.indexOf(expected_res);
            measure_value = measure_value.substring(offset + expected_res.length());
            Serial.println("EDATA = " + measure_value);
            if(!(measure_value.indexOf("72") != -1 || measure_value.indexOf("52") != -1)){
               Serial.println("measure res data err");
               loop_cnt++;
               continue;
            }

            Serial.println("measure res OK");

            //係数:0xD3
            offset = measure_value.indexOf("D3");
            start_data = offset + 2;
            end_data = start_data + 2;
            hex_PDC = measure_value.substring(start_data, end_data);
            int_PDC = hexToDec(hex_PDC);
            Serial.println("D3 PDC " + hex_PDC + ", " + String(int_PDC, DEC));
            start_data = end_data;
            end_data = start_data + 2 * int_PDC;
            String str_coefficient = measure_value.substring(start_data, end_data);
            Serial.println("str_coefficient:" + str_coefficient);
            uint32_t int_coef = hexToDec(str_coefficient);
            Serial.println("coefficient:" + String(int_coef));
            coefficient_ = float(int_coef);

            measure_value = measure_value.substring(end_data);

            //積算電力量単位:0xE1
            offset = measure_value.indexOf("E1");
            start_data = offset + 2;
            end_data = start_data + 2;
            hex_PDC = measure_value.substring(start_data, end_data);
            int_PDC = hexToDec(hex_PDC);
            Serial.println("E1 PDC " + hex_PDC + ", " + String(int_PDC, DEC));
            start_data = end_data; 
            end_data = start_data + 2 * int_PDC;
            String str_power_unit = measure_value.substring(start_data, end_data);
            Serial.println("str power unit:" + str_power_unit);
            uint32_t int_power_unit = hexToDec(str_power_unit);
            Serial.println("power unit:" + String(int_power_unit));
            integral_power_unit_ = convertPowerUnit(int_power_unit);

            measure_value = measure_value.substring(end_data);

            //積算履歴収集日１:0xE5
            offset = measure_value.indexOf("E5");
            start_data = offset + 2;
            end_data = start_data + 2;
            hex_PDC = measure_value.substring(start_data, end_data);
            int_PDC = hexToDec(hex_PDC);
            Serial.println("E5 PDC " + hex_PDC + ", " + String(int_PDC, DEC));
            start_data = end_data;
            end_data = start_data + 2 * int_PDC;
            String str_collect_date = measure_value.substring(start_data, end_data);
            uint32_t int_collect_date = hexToDec(str_collect_date);
            Serial.println("collect date:" + String(int_collect_date));
            accumulation_collection_date_ = int_collect_date;
            if(accumulation_collection_date_ != 0){
                Serial.println("collect date is not today!");
            }

            measure_value = measure_value.substring(end_data);

            //定時積算電力量計測値(正方向計測値):0xEA
            offset = measure_value.indexOf("EA");
            start_data = offset + 2;
            end_data = start_data + 2;
            hex_PDC = measure_value.substring(start_data, end_data);
            int_PDC = hexToDec(hex_PDC);
            Serial.println("EA PDC " + hex_PDC + ", " + String(int_PDC, DEC));
            start_data = end_data;
            end_data = start_data + 2 * int_PDC;
            String str_integral_power = measure_value.substring(start_data, end_data);
            
            Serial.println(str_integral_power);
            String str_year = str_integral_power.substring(0, 4);
            integral_power_consumpution_.year = hexToDec(str_year);
            String str_month = str_integral_power.substring(4, 6);
            integral_power_consumpution_.month= hexToDec(str_month);
            String str_day = str_integral_power.substring(6, 8);
            integral_power_consumpution_.day = hexToDec(str_day);
            String str_hour = str_integral_power.substring(8, 10);
            integral_power_consumpution_.hour = hexToDec(str_hour);
            String str_minute = str_integral_power.substring(10, 12);
            integral_power_consumpution_.minute = hexToDec(str_minute);
            String str_sec = str_integral_power.substring(12, 14);
            integral_power_consumpution_.sec = hexToDec(str_sec);
            String str_power_consumption = str_integral_power.substring(14, 22);
            integral_power_consumpution_.power_consumpution = hexToDec(str_power_consumption);

            Serial.println("Date " + String(integral_power_consumpution_.year) + ":" 
                            + String(integral_power_consumpution_.month) + ":"
                            + String(integral_power_consumpution_.day) + ":" 
                            + String(integral_power_consumpution_.hour) + ":" 
                            + String(integral_power_consumpution_.minute) + ":" 
                            + String(integral_power_consumpution_.sec)
                            );

            //係数 * 積算電力量単位 * 時積算電力量計測値 
            integral_power_consumpution_.power_consumpution = 
            (uint32_t)(integral_power_consumpution_.power_consumpution *  
                        coefficient_ * integral_power_unit_);
            Serial.println("power consumption:" + String(integral_power_consumpution_.power_consumpution) + "[kWh]");
            *integral_power = integral_power_consumpution_;
            return true;
        }while(loop_cnt < MEASURE_LIMIT);

        return false;
 
    };
```

瞬時電力量との違いは、係数、積算電力量単位、積算電力量、積算履歴収集日を同時に所得しています。

その場合は、

``` BP35A1.h
        //OPC(4個)
        data_str[11] = char(0x04);
```
として取得したいパラメータ数をOPCにセット、その後

``` BP35A1.h
        //EPC
        //係数:0xD3
        data_str[12] = char(0xd3);
        //PDC read:0
        data_str[13] = char(0x00);
```
として、EPC,PDCを各パラメータごとにセットしていけば送信側のデータはセット出来ます。

受信側は

``` BP35A1.h
            //係数:0xD3
            offset = measure_value.indexOf("D3");
            start_data = offset + 2;
            end_data = start_data + 2;
            hex_PDC = measure_value.substring(start_data, end_data);
            int_PDC = hexToDec(hex_PDC);
            Serial.println("D3 PDC " + hex_PDC + ", " + String(int_PDC, DEC));
            start_data = end_data;
            end_data = start_data + 2 * int_PDC;
            String str_coefficient = measure_value.substring(start_data, end_data);
            Serial.println("str_coefficient:" + str_coefficient);
            uint32_t int_coef = hexToDec(str_coefficient);
            Serial.println("coefficient:" + String(int_coef));
            coefficient_ = float(int_coef);
```

こういった形で応答する値のIndex(ここではD3)を検索する形でデータを取り出します。
基本的にこの繰り返しです。

# ambientに電力量をアップロード
ambientへのデータの保存はサンプルがあるのでそれを参考にしました。
https://ambidata.io/docs/esp8266/

``` M5Stack_SmartMeter.ino
void setupAmbient(void)
{
  ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化
}
```

取得した温度、湿度、気圧、瞬時電力量[W], 定時積算電力量計測値のアップロードはこういうコードを書けば実行できます。
アップロードは5分置きに実行しています。

``` M5Stack_SmartMeter.ino
  // send BME280 data to ambient
  ambient.set(1, String(bme_data.temperature).c_str());
  ambient.set(2, String(bme_data.humidity).c_str());
  ambient.set(3, String(bme_data.pressure / 100).c_str());
  
  // send BP35A1 power to ambient
  ambient.set(4, String(integral_power.power_consumpution).c_str());
  ambient.set(5, String(instantaneuous_power).c_str());
```

# ambientでグラフを確認
各パラメータはこういった形で表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/302503/104c3155-35fc-752e-4f65-f70523d8108b.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/302503/597f1507-ff02-bbb6-8a81-8ec6913aa629.png)

特に瞬時電力量を一日取得したグラフを見ると、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/302503/cde83af4-6cab-cbe8-26ce-5da36c617a95.png)

就寝している間はほぼ電力量300W以下に抑えられていますが、起きてエアコンをつけると一気に2000W以上になっていることがわかります。また、たまに電力量が多い時間は電子レンジやドライヤーを使っている時間帯に一致していたので、何か熱系のモノを使用すると電力量が多くなっているかがわかります。結構見ているだけでも面白いです。
17時以降は5分置きに大きく上下している箇所が非常に多いのはなぜなんでしょう、そこはちょっと気になるところです。

# 最後に
スマートメーターで取得した電力をグラフ化することが出来るようになりました。
これで冬場の膨大な電気代を抑えるための対策が立てられる？かもしれないです。