M5StickCを使ってスマートメーターから電気使用量を表示した

2021年2月21日

今の家庭用電力計はスマートメーターに置き換わっている

今の電力系というのはスマートメーターに置き換わっていてここから電気使用量などを個人で取得できるようになっているようです。

パッケージ化されていてわかりやすいところだとスマートリモコン等を販売しているNature株式会社よりNature Remo Eという商品が販売されています。

Nature スマートエナジーハブ Nature Remo E

Nature スマートエナジーハブ Nature Remo E

39,800円(11/21 20:20時点)
Amazonの情報を掲載しています
スマートエナジーハブ Nature Remo E lite

スマートエナジーハブ Nature Remo E lite

14,800円(11/21 20:20時点)
Amazonの情報を掲載しています

ところがNature Remo Eを使った場合は表示デバイスがスマートフォン等になるため、すぐに使用電力をサクッと確認するには手間です、ここでM5StickCというIoTデバイスを使って表示する仕組みを構築している方がいらっしゃったのでこちらの仕組みを利用させていただきました。

とりあえず動作させるところまでのメモ

大まかな仕組みや機材はこちらのnorifumiさんのBLOGを参考にさせていただきました。

ただしこちらに掲載されているプログラムの場合は再接続処理が組み込まれておらず、うちのスマートメーターの場合は24時間程度でセッションが切れてしまうためこの処理がない場合は電力の取得がその時点で止まってしまいます。

そのためこのBLOGの謝辞欄に掲載されていたmiyaichiさんのプログラムを利用させていただきました。

こちらもM5StickCを使ってはいるのですが古めのバージョンのUIFlow上で作成されているらしく、私の使用したUIFlowのバージョン1.7.2ではプログラムをReadmeの通りに転送しても動かなかったので一部改変しています。

loggingモジュールが存在しない

まず今のUIFlowにはloggerモジュールが存在していないのでここで躓きました。
こちらは適当にググって引っかかったgithubからlogging.pyをダウンロードしたところ解消しました。

wifiCfgの関数が違っている

バージョンの違いからか、掲載されているプログラムではwifiCfg.isconnectedが使われていましたが、こちらは今のバージョンだとwifiCfg.is_connectedになっているようでこの部分も修正しました。

ntptimeの関数が違っている

ntptimeは以前は外部からダウンロードしたモジュールをアップロードして使う形でしたが、UIFlowに内蔵されるようになったうえ使用する際の関数が変わったようです。

プログラム内ではntptime.settime()となっていましたがntp = ntptime.client(host=’jp.pool.ntp.org’, timezone=9)というように書き換えたところうまく動作したようです。

差分内容

上で書いたSMM.pyの中身の改変内容のdiffです。

--- SMM.py      2021-01-22 10:54:47.000000000 +0900
+++ SMM_new.py  2021-01-22 13:08:16.000000000 +0900
@@ -55,7 +55,7 @@ def checkWiFi():
     """
     WiFi接続チェック
     """
-    if not wifiCfg.isconnected():
+    if not wifiCfg.is_connected():
         logger.warn('Reconnect to WiFi')
         if not wifiCfg.reconnect():
             machine.reset()
@@ -175,7 +175,7 @@ if __name__ == '__main__':
         # Connecting Wi-Fi
         status('Connecting Wi-Fi')
         wifiCfg.autoConnect(lcdShow=False)
-        if not wifiCfg.isconnected():
+        if not wifiCfg.is_connected():
             raise Exception('Can not connect to WiFi.')

         # Start checking the WiFi connection
@@ -185,7 +185,7 @@ if __name__ == '__main__':

         # Set Time
         status('Set Time')
-        ntptime.settime()
+        ntp = ntptime.client(host='jp.pool.ntp.org', timezone=9)

         # Load configuration
         status('Load configuration')

2021/02/01 追記 検針日の計算やLCDの表示周りがうまく動作していなかったようなのでBP35A1.pyも修正しました
検針日の計算がうまく動作しておらず、積算電力量とその料金計算がうまく動作していませんでした。
またLCDに表示される検針起算日についてもここの数値をベースにしているようで動いていなかったため修正してみました。

--- BP35A1_org.py       2020-07-07 15:10:29.000000000 +0900
+++ BP35A1.py   2021-02-03 16:22:26.349156400 +0900
@@ -67,8 +67,7 @@ def days_of_year(y, m, d):


 def localtime():
-    offset = 9 * 3600  # JST
-    return utime.localtime(utime.mktime(utime.localtime()) + offset)
+    return utime.localtime()


 def strftime(tm, *, fmt='{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}'):
@@ -78,19 +77,23 @@ def strftime(tm, *, fmt='{:04d}-{:02d}-{

 def days_after_collect(collect_mday):
     (year, month, mday) = localtime()[:3]
-    if month == 1:
+    if month == 1 and mday < collect_mday:
         return 31 - collect_mday + mday
     days1 = days_of_year(year, month, mday)
     if mday < collect_mday:
         month -= 1
-    days2 = days_of_year(year, month, collect_mday)
+        days2 = days_of_year(year, month, collect_mday)
+    else:
+        days2 = days_of_year(year, month, collect_mday)
     return days1 - days2


 def last_colect_day(collect_mday):
     (year, month, mday) = localtime()[:3]
     if month == 1:
-        return (year - 1, 12, 31 - collect_mday + mday)
+        tm = year - 1, 12, collect_mday
+        tm_out = str(tm[0] -1)+'-'+str(tm[1])+'-'+str(tm[2])
+        return tm_out
     if mday < collect_mday:
         month -= 1
     return strftime((year, month, collect_mday, 0, 0, 0))

またうちの場合は電力会社としてサニックス電気のサニックステラセーバーS(関東)を契約して使っているのでその内容をcharge.pyに追記した差分がこちらです

--- charge_org.py       2020-07-07 15:10:29.000000000 +0900
+++ charge.py   2021-01-22 21:16:36.758564100 +0900
@@ -94,5 +94,34 @@ def tokyo_gas_2(contract, power):
     return int(fee)


+def sanix(contract, power):
+    """
+    サニックスでんき「サニックステラセーバーS(関東)」での電気料金計算
+
+    Parameters
+    ----------
+    contract : str
+        契約アンペア数
+    power : float
+        前回検針後の使用電力量(kWh)
+
+    Returns
+    -------
+    fee: int
+        電気料金
+    """
+    fee = {
+        '10': 743.60,
+        '15': 743.60,
+        '20': 743.60,
+        '40': 743.60,
+        '50': 929.50,
+        '60': 1115.4
+    }[contract]
+
+    fee += 23.22 * power
+
+    return int(fee)
+
 if __name__ == '__main__':
     print(tokyo_gas_1('50', 339))

動作状態

修正を施して動作した状態が下の画像です。

ちょうどこれを書いているのが検針日になるので、検針日を過ぎたら電気料金がリセットされるかどうかまではまだ未検証ですがとりあえずは問題なさそうです。
それにしても概算予想とはいえ今月の電気代やばいですね。

2021/02/01 追記 やはり検針日の部分の計算うまくいってなかったので、上記BP35A1.pyの修正をして動作中の様子が下の画像になります。

解決していない事象と、遭遇した事象

解決していない事象としては、シリアルコンソールでログを見ていると定期的に「TypeError: function takes 0 positional arguments but 1 were given」というエラーが出るのですが、これは原因がわからず解消できていません。
とりあえず動作はしているので放置している状態です。

また遭遇した事象としては、Bルートに申し込んだ直後だったりしてスマートメーターに蓄積されている積算電力値の履歴以上の日数分の電力値を要求すると取得できる値がマイナスになるケースがあるようです。

おわりに

一応わかる範囲で修正はしたのですが、自分の場合スキルレベルとしてなんとなくWEBを片手に見ながら切った貼ったでシェルスクリプトが読めたりちょっと書けるというレベルの人間なので今回のpythonについても同様の手順で行ってます。

WEBを見ながらひたすら実機上でひとつづつ気になったところを小分けのプログラムにして確認するという手法で直しただけなので、まだまだ動作のおかしいところなどはあるかもしれないのでご容赦ください。