M5Stackでスマートメーターの電力量から今月の電気料金を計算・表示

# はじめに

いままでM5Stack(ESP32)を使って、スマートメーターと通信して瞬時電力量や積算電力量をAmbientでロギングしてきました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/302503/309d7f39-3198-9263-8e0a-58b775fbd3e1.png)

[M5Stackでスマートメーターの瞬時電力をBP35A1から取得して表示してみた](https://qiita.com/KatsuShun89/items/7a09917bb813b42bb9d1)
[M5Stackでスマートメーターの電力量を取得してAmbientでグラフ表示](https://qiita.com/KatsuShun89/items/8dcc11941588b8ab69e3)

基本的なスマートメーターとの通信は出来たので、今回はそれの応用で、今月分の電気料金を計算するデバイスを作ってみました。
今回のソースコードも前回と同じハードウェア構成ですので、そのあたりは過去の記事を参考にしてください。

# ソースコード
ソースコードはこちらで公開しています。
https://github.com/Katsushun89/M5Stack_SmartMeter/tree/electric_bill_calculation
electric_bill_calculationブランチを使ってください。

スマートメーターとの通信はBP35A1で行っています。
ソースコード上は前回同様に環境センサーのbme280で温度・気圧・湿度も取得しています。

# 電気料金の算出方法
前提として東京電力を使っているものとします。
電気料金はこのサイトで計算出来ます。その算出方法も確認出来ます。

[でんき家計簿 電気料金計算サービス](https://www.kakeibo.tepco.co.jp/ratesim/)

我が家は「従量電灯Ｂ」ですので、ソースコードもこれベースになっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/302503/e909be86-282f-17c0-f68a-701c3b974777.png)

ここに必要事項を入力すると、毎月ポストに入っている電気料金代の紙と同等の計算ができます。

例えば、
- 使用量：500 [kWh]
- ご契約：50 [A]
とすると、合計金額：15,365円と出てきました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/302503/709fbe81-4fb8-7c6a-6f11-0b5bfe1b6dfb.png)

ここで「内訳はこちら」を押すと、下記のような算出方法が出てきます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/302503/70712f3d-2c8e-2510-6dce-de2be6935507.png)

これを見ると、以下のことがわかります。
- 基本料金はアンペア数によって決まる。つまり固定値。
- 1ヶ月の電力量（＝使用量）から電力量料金は算出可能
- その他の値も1ヶ月の電力量がわかれば算出可能、もしくは固定値

つまりは**検針日から現在までの電力量の差分値**がわかれば、電気料金は算出することが出来ます。（まー当たり前といえば当たり前ですね。）

ただ、昼と夜で電気料金の係数が異なるような料金プランの場合はもう少し高度なことをやらないといけないです。（今回は面倒なのでそこまではやらないです。）

電気料金の算出方法はわかったので、実際に下記のような定義値と計算式から電気料金が算出します。

```config_electric_bill.h
// 従量電灯Ｂ meter rate lighting
const float BASIC_CHARGE = 1123.00;

const float PHASE_1_UNIT_CHARGE = 19.52; //最初の120kWhまで（第１段階料金）
const float PHASE_2_UNIT_CHARGE = 26.00; //120kWhをこえ300kWhまで（第２段階料金）
const float PHASE_3_UNIT_CHARGE = 30.02; //上記超過（第３段階料金）
const float FUEL_COST_ADJUSTMENT = -0.66; //燃料費調整額

const float PHASE_1_MAX_POWER_CONSUMPTION = 120; //[kWh]
const float PHASE_2_MAX_POWER_CONSUMPTION = 300; //[kWh]

const float ADDITIONAL_LEVY_1_UNIT_CHARGE = 2.90; //再生可能エネルギー発電促進賦課金

const float DISCOUNT_AMOUNT = 54; //口座振替割引額	
```

```electric_bill_calculation.cpp
float ElectricBillCalculation::calcMeterRateLightingB(void)
{
    electric_bill_ = BASIC_CHARGE;

    if(power_consumption_this_month_ > PHASE_2_MAX_POWER_CONSUMPTION){
        electric_bill_ += (power_consumption_this_month_ - PHASE_2_MAX_POWER_CONSUMPTION) * PHASE_3_UNIT_CHARGE;
        electric_bill_ += (PHASE_2_MAX_POWER_CONSUMPTION - PHASE_1_MAX_POWER_CONSUMPTION) * PHASE_2_UNIT_CHARGE;
        electric_bill_ += (PHASE_1_MAX_POWER_CONSUMPTION) * PHASE_1_UNIT_CHARGE;
    }else if(power_consumption_this_month_ > PHASE_1_MAX_POWER_CONSUMPTION){
        electric_bill_ += (power_consumption_this_month_ - PHASE_1_MAX_POWER_CONSUMPTION) * PHASE_2_UNIT_CHARGE;
        electric_bill_ += (PHASE_1_MAX_POWER_CONSUMPTION) * PHASE_1_UNIT_CHARGE;
    }else if(power_consumption_this_month_ <= PHASE_1_MAX_POWER_CONSUMPTION){
        electric_bill_ += (power_consumption_this_month_) * PHASE_1_UNIT_CHARGE;
    }else{
        Serial.println("Error calc power consumption");
    }

    electric_bill_ += power_consumption_this_month_ * FUEL_COST_ADJUSTMENT;

    electric_bill_ += power_consumption_this_month_ * ADDITIONAL_LEVY_1_UNIT_CHARGE;

    electric_bill_ -= DISCOUNT_AMOUNT;
    
    Serial.println("calcMeterRateLightingB " + String(electric_bill_));
    return electric_bill_;
}
```
これはもう算出方法をコードにしただけなので、見てもらえばだいたい理解出来る前提で説明は省きます。

# 今月の電力量の取得
今月の電力量（＝検針日から現在の電力量）がわかれば、電気料金は算出出来る状況になりました。
そのために
- 現在の日時をNTPで設定
- 検針日が現在から何日前なのか計算
- 検針日の電力量の取得
をします。

## 現在の日時をNTPで設定
この方法はネットで調べれば情報出てきます。Wifi設定後に下記のコードでNTPの時刻同期出来ます。

```cpp:M5Stack_SmartMeter.ino
time_t t;
configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");//after setupWifi
```

time_tからは下記のコードで年月日を取れます。

```electric_bill_calculation.cpp
time_t t;
struct tm *tm_today;
tm_today = localtime(&t);
int16_t y = tm_today->tm_year + 1900;
int16_t m = tm_today->tm_mon + 1;
int16_t d = tm_today->tm_mday;
```

## 検針日が現在から何日前なのか計算

現在の年月日がわかると、次に検針日が何日前か求める必要があります。
それは検針日の電力量を求めるにはEchonet下記の2つのコマンドを使うためです。

- 積算履歴収集日1:0xE5
  - 0:当日、1～99:前日の日数
  - 前回の検針日までの日数をスマートメーターに設定
- 積算電力量計測値履歴１(正方向計測値):0xE2
  - 積算履歴収集日１と該当収集日の24時間48コマ分（0時0分～23時30分）の正方向の定時積算電力量計測値の履歴データを時系列順に上位バイトからプロパティ値として示す
  - 1～2バイト目：積算履歴収集日0x0000～0x0063(0～99)
  - 3バイト目以降：積算電力量計測値0x00000000～0x05F5E0FF(0～99,999,999)


指定した日の積算電力量を求める場合は、何日前かの電力量が取得するか設定する必要があります。
検針日ですが、私の家では**毎月12日**が検針日になっています。
configとしては12日というところだけ定数で持っていれば、現在の日が12日以下なら前月、それ以上なら今月として日数の差分を取れば、検針日までの日数が取れます。


検針日の電力量を取得するまでの流れを示すコードはこのような流れになります。

```cpp:M5Stack_SmartMeter.ino
void executeInitialCommBP35A1(void)
{
  time_t t = time(NULL);
  //検針日が何日前か日数を計算してcollect_dateに代入
  uint8_t collect_date = bill_calc.calcMeterReadingDiffDays(&t);

  //積算履歴収集日1:0xE5
  //0:当日、1～99:前日の日数
  //前回の検針日までの日数をスマートメーターに設定
  if(bp35a1->setIntegralCollectDate(collect_date)){
    Serial.println("setIntegralCollectDate success");
  }
   
  //積算電力量計測値履歴１(正方向計測値):0xE2
  //積算履歴収集日1で設定した日付の積算電力量の30分ごとのリストを24時間48コマ分取得
  //ここで検針日の電力量のリストが取得出来る
  integral_power_record_t integral_power_record = {0};
  if(bp35a1->getIntegralPowerRecord(&integral_power_record)){
    Serial.println("getIntegralPowerRecord success");
  }

  //検針日の積算電力量を保存
  bill_calc.setMeterReadingPowerConsumption(&integral_power_record);
}

```
setIntegralCollectDate(),getIntegralPowerRecord()などの詳細は公開しているコードを参照してください。


ここまでくれば検針日の積算電力量は30分ごとのリストで取得出来るので、その最小値を検針日の電力量として保持すれば良いです。

あとは、前回の記事同様に、定時積算電力量計測値(正方向計測値)で現在の電力量を取得して、差分を取ると今月の電力量がわかります。
それを最初の電気料金計算の式に入れれば完了です。

それを表示した結果がこちら。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/302503/309d7f39-3198-9263-8e0a-58b775fbd3e1.png)

1MTH IPC：今月の電力量
BILL：今月の電気料金
です。

# 最後に
前回、電力量の取得・表示を投稿したら、電気料金も求められたら嬉しいとご意見もらえたので試しに作ってみました。
あまり小難しいことはないので、最初はすぐに出来ると思っていたのですが、やってみるとやること多くて時間がかかってしまいました。

一応実際にポストに入っていた今月の電気料金代と、計算した値はほぼ一致していたので（誤差50円程度）、だいたいの電気料金はこれでわかりそうです。

これをベースにConfig設定を変えるだけで使用できるようにしているので、もしよければ使ってみてください。