Nature Remo E liteで、おうちの消費電力を見える化してみた

#はじめに
家庭の電気使用量をリアルタイム取得可能な「Nature Remo E lite」を使い、
消費電力を見える化するダッシュボードを作ってみました
![watt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/9bed27c2-eb03-c5e8-6735-582354eb6d42.png)
Nature Remoで取得したエアコンの使用状況も一緒に表示することで、
**消費電力とエアコンOn-Offとの関係が一目でわかるようになりました！**
省エネハウス実現に向けて、一歩前進できたと感じています。

#IoTとスマートメーター
**IoT**という言葉がブームとなって久しいですが、
同時にIoTで価値を生み出すことの難しさも浸透しつつあり、世の中のIoTに対する目が厳しくなっていると感じます。

下の図はガートナー社発表の2019年度版"ハイプサイクル図"ですが、
IoTは見事に幻滅期に入ってしまっています。
![191031_gartner_hypecycle_2019.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/0ea912ab-1095-c08d-e185-070b949520a5.png)


特に家庭向けIoT製品は、一世を風靡したスマートスピーカー＆スマートリモコンを除くと、
スマート化してもあまりメリットを感じられない、というものが多いように感じます。

このような中、電力会社が着々と導入を進めているIoT製品が**スマートメータ**です。
家庭、そして社会の一大コストである消費電力を見える化して効率利用できれば、
**「価値」**を生み出せるのでは、という考えに基づいています。

#Nature Remo E liteとは？
上記のスマートメータは、WiSUNという無線規格で消費電力データを送信していますが、
このデータをサードパーティ製品で受信して利用 (Bルートサービス)することもできます。

このようなサードパーティ製品のうち、
スマートリモコン「Nature Remo」で有名なNature社が開発した**消費電力データ受信デバイス**が、

**Nature Remo E lite**です。
![remo-e-lite.12.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/cdc100d2-5db4-04c6-3e22-0fc7ef4cc6e6.jpeg)

#できること
Nature Remo E liteを使うと、以下のようなことができます。
##・アプリで消費電力確認
公式アプリで、現在の消費電力や、簡単な履歴グラフを確認することができます
![Screenshot_20200812-174424.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/f0b804ba-80ba-cdfc-d267-a6c2b823aa34.png)

##・APIで消費電力データ取得
APIを使って、消費電力データをプログラム上で扱うことができます。
基本的には、[Nature Remoと共通のAPI](https://swagger.nature.global/#/)で取得可能です。

`GET /1/appliances`

というコマンドで、Nature Remoのデバイスデータと一緒に、Remo E liteの消費電力データも取得できます。

###API実行例
Linuxで下記コマンドを打ちます
([アクセストークンの発行法はこちらをご参照ください](https://qiita.com/t-chi/items/01b9a9b98fbccef880c3#natureremo%E3%81%AE%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E7%99%BA%E8%A1%8C%E3%81%99%E3%82%8B))

```
curl -X GET "https://api.nature.global/1/appliances" -k --header "Authorization: Bearer アクセストークン"
```

以下のようなJSONが返ってきます

```json
[
    {
    ：
    他のデバイス情報
    ：
    },
    {
        "id": "****",
        "device": {
            "name": "Remo E lite",
            "id": "****",
            "created_at": "2020-06-09T17:16:06Z",
            "updated_at": "2020-06-17T13:55:50Z",
            "mac_address": "**:**:**:**:**:**",
            "bt_mac_address": "**:**:**:**:**:**",
            "serial_number": "****",
            "firmware_version": "Remo-E-lite/1.1.3",
            "temperature_offset": 0,
            "humidity_offset": 0
        },
        "model": {
            "id": "****",
            "manufacturer": "",
            "name": "Smart Meter",
            "image": "ico_smartmeter"
        },
        "type": "EL_SMART_METER",
        "nickname": "スマートメーター",
        "image": "ico_smartmeter",
        "settings": null,
        "aircon": null,
        "signals": [],
        "smart_meter": {
            "echonetlite_properties": [
                {
                    "name": "cumulative_electric_energy_effective_digits",
                    "epc": 215,
                    "val": "7",
                    "updated_at": "2020-08-12T10:09:14Z"
                },
                {
                    "name": "normal_direction_cumulative_electric_energy",
                    "epc": 224,
                    "val": "294263",
                    "updated_at": "2020-08-12T10:09:14Z"
                },
                {
                    "name": "cumulative_electric_energy_unit",
                    "epc": 225,
                    "val": "2",
                    "updated_at": "2020-08-12T10:09:14Z"
                },
                {
                    "name": "measured_instantaneous",
                    "epc": 231,
                    "val": "464",
                    "updated_at": "2020-08-12T10:09:14Z"
                }
            ]
        }
    },
    {
    ：
    他のデバイス情報
    ：
    }
]
```

スマートメータの情報は、
"smart_meter": "echonetlite_properties"セクション内の"val"に記載されています。
その中でも特に重要なのが、下の3つのセクションです。

|セクション名|内容|単位|
|---|---|---|
|normal_direction_cumulative_electric_energy|今までの総消費電力量|`cumulative_electric_energy_unit`に記載された値に依存<br>(例:cumulative_electric_energy_unit=2なら0.01kWh、1なら0.1kWh)|
|cumulative_electric_energy_unit|上記総消費電力量の単位|kWhの小数点以下桁数（上述）|
|measured_instantaneous|現在の消費電力|Watt|

上のJSONの場合、総消費電力量2942.63kWh、現在の消費電力464Wattとなります。
※セクション内容はRemo E liteのアップデートにより変わる可能性があるのでご注意ください

**今回はこのAPIを利用して、トップ画像のように消費電力とエアコンOn-Offの関係をグラフに表示します**

#必要なもの
**・Nature Remo E lite**
**・[通常のNature Remo](https://www.amazon.co.jp/gp/product/B08BLSLWH4/)**‥エアコンのOn-Off情報取得に必要
**・Raspberry Pi** (今回はRaspberry Pi3 ModelBを使用)
**・Python実行環境**（今回はプリセットのPython3.7.3使用）
**・Googleアカウント**（スプレッドシート＆データポータル利用に必要）

#手順
下記の手順で、消費電力のダッシュボードを作成します。

**1. 電力会社にBルートサービス申請
2. Nature Remo E liteの初期設定
3. Raspberry Piによるロギングシステムの構築
4. 消費電力ダッシュボードの作成**

##1. 電力会社にBルートサービス申請
スマートメータのデータを取得するためには、使用中の電力会社にBルートサービスの申請をする必要があります。
（電力自由化でauでんきのようなサービスを利用している場合も、東京電力のようなエリア電力会社に申し込む必要があるようです）

###電力会社ホームページから申し込み
私の場合、下記関西電力グループのホームページから申し込みました
https://www.kansai-td.co.jp/application/smart-meter/low-pressure/index.html

それ以外の電力会社エリアでは、下記ご参照ください
https://chasuke.com/nremoe/

申し込みをすると電話が掛かってきて、簡単な工事(無料)が必要とのことで日程調整→工事実施という流れとなりました。

工事の必要有無はその家のスマートメータ導入状況によって異なります。
2020年現在、全国の家庭のおよそ2/3がスマートメータ化が進んでいるそうですが、
・スマートメータ化されていない家庭の場合、上記申し込み時に優先して置き換えの申請ができる
・スマートメータ化されていたとしても、私のように追加の工事が必要な場合もある
ようです。

###BルートサービスIDとパスワードの受け取り
上記申込と工事が完了すると、電力会社よりBルートサービスのIDとパスワードが郵送で送られてきます。
東京電力ではパスワードがメールで送られてきたりと、送付方法はエリアによって異なるようです（いずれにせよ、メールか郵送のどちらかだと思います）

##2. Nature Remo E liteの初期設定
###Nature Remo E liteをコンセントに挿す
Nature Remo E liteをコンセントに挿します。
配電盤から近いコンセントに挿した方が、後で同期をとる際に上手くいくことが多いように感じました。
![IMG_20200812_172047.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/21aa8470-cc41-fe44-905a-876707328e94.jpeg)

###Nature Remoアプリのダウンロード
下のサイトからスマホにアプリをダウンロードし、インストールします。
[android](https://play.google.com/store/apps/details?id=global.nature.remo&hl=ja)
[iOS](https://apps.apple.com/jp/app/nature-remo/id1193531669)

###初期設定
基本的にはアプリの指示に従って初期設定を進め、
途中でIDとパスワードを求められたら、電力会社より郵送されてきた内容を入力します。

メータやWiFiと同期をとる際に失敗することがありますが、私
の場合何度かやり直せば上手くいく事が多かったです。

##3. Raspberry Piによるロギングシステムの構築
以前開発したセンサデータのロギングシステムを流用します。
[まずはこちらの記事の内容を実施してください(長いですが、お付き合いいただけるとありがたいです)](https://qiita.com/c60evaporator/items/283d0569eba58830f86e)

今回は上記システムをNature Remo E liteに対応させるため、
remo.pyおよびsensors_to_spreadsheet.pyを書き換える必要があります。
それぞれ下記の内容に書き換えてください。
（上の[API実行例]で返ってきたJSONを、ロギング対象に組み込む操作に相当します）

```remo.py
import json
import requests
import glob
import pandas as pd

#Remoデータ取得クラス
class GetRemoData():
    def get_sensor_data(self, Token, API_URL):
        headers = {
            'accept': 'application/json',
            'Authorization': 'Bearer ' + Token,
        }
        response = requests.get(f"{API_URL}/1/devices", headers=headers)
        rjson = response.json()
        return self._decodeSensorData(rjson)

    def get_aircon_power_data(self, Token, API_URL):
        headers = {
            'accept': 'application/json',
            'Authorization': 'Bearer ' + Token,
        }
        response = requests.get(f"{API_URL}/1/appliances", headers=headers)
        rjson = response.json()
        return self._decodeAirconPowerData(rjson)

    def calc_human_motion(self, Human_last, csvdir):
        filelist = glob.glob(f"{csvdir}/*/*.csv")
        if len(filelist) == 0:
            return 0
        filelist.sort()
        df = pd.read_csv(filelist[-1])
        if df.Human_last[len(df) - 1] != Human_last:
            return 1
        else:
            return 0

    # センサデータを取り出してdict形式に変換
    def _decodeSensorData(self, rjson):
        for device in rjson:
            #Remoのデータ
            if device['firmware_version'].split('/')[0] == 'Remo':
                sensorValue = {
                    'SensorType': 'Remo_Sensor',
                    'Temperature': device['newest_events']['te']['val'],
                    'Humidity': device['newest_events']['hu']['val'],
                    'Light': device['newest_events']['il']['val'],
                    'Human_last': device['newest_events']['mo']['created_at']
                }
        return sensorValue

    # エアコンおよび電力データを取り出してdict形式に変換
    def _decodeAirconPowerData(self, rjson):
        Value = {}
        for appliance in rjson:
            #エアコン
            if appliance['type'] == 'AC':
                Value['TempSetting'] = appliance['settings']['temp']
                Value['Mode'] = appliance['settings']['mode']
                Value['AirVolume'] = appliance['settings']['vol']
                Value['AirDirection'] = appliance['settings']['dir']
                Value['Power'] = appliance['settings']['button']
            #スマートメータの電力データ
            elif appliance['type'] == 'EL_SMART_METER':
                for meterValue in appliance['smart_meter']['echonetlite_properties']:
                    if meterValue['name'] == 'normal_direction_cumulative_electric_energy':
                        Value['CumulativeEnergy'] = float(meterValue['val'])/100
                    elif meterValue['name'] == 'measured_instantaneous':
                        Value['Watt'] = int(meterValue['val'])        
        #値を取得できていないとき、Noneとする
        if len(Value) == 0:
            Value = None
        return Value
```
上記の`get_aircon_power_data`および`_decodeAirconPowerData`メソッドで、消費電力データを取得しています。

```sensors_to_spreadsheet.py
from bluepy import btle
from omron_env import OmronBroadcastScanDelegate, GetOmronConnectModeData
from inkbird_ibsth1 import GetIBSTH1Data
from switchbot import SwitchbotScanDelegate
from remo import GetRemoData
from mesh import GetMeshFromSpreadsheet
from datetime import datetime, timedelta
import os
import csv
import configparser
import pandas as pd
import requests
import logging
import subprocess
import pymongo
from pit import Pit

#グローバル変数(取得時刻)
global masterdate

######オムロン環境センサ（BAG型）の値取得######
def getdata_omron_bag(device):
    #値が得られないとき、最大device.Retry回スキャンを繰り返す
    for i in range(device.Retry):
        #omron_envのセンサ値取得デリゲートを、スキャン時実行に設定
        scanner = btle.Scanner().withDelegate(OmronBroadcastScanDelegate())
        #スキャンしてセンサ値取得
        try:
            scanner.scan(device.Timeout)
        #スキャンでエラーが出たらBluetoothアダプタ再起動
        except:
            restart_hci0(device.DeviceName)
        #値取得できたらループ終了
        if scanner.delegate.sensorValue is not None:
            break
        #値取得できなかったらログに書き込む
        else:
            logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
    
    #値取得できていたら、POSTするデータをdictに格納
    if scanner.delegate.sensorValue is not None:
        #POSTするデータ
        data = {        
            'DeviceName': device.DeviceName,        
            'Date_Master': masterdate,
            'Date': datetime.today(),
            'Temperature': scanner.delegate.sensorValue['Temperature'],
            'Humidity': scanner.delegate.sensorValue['Humidity'],
            'Light': scanner.delegate.sensorValue['Light'],
            'UV': scanner.delegate.sensorValue['UV'],
            'Pressure': scanner.delegate.sensorValue['Pressure'],
            'Noise': scanner.delegate.sensorValue['Noise'],
            'BatteryVoltage': scanner.delegate.sensorValue['BatteryVoltage']
        }
        return data
    #値取得できていなかったら、ログ出力してBluetoothアダプタ再起動
    else:
        logging.error(f'cannot get data [date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
        restart_hci0(device.DeviceName)
        return None

######オムロン環境センサ（USB型）のデータ取得######
def getdata_omron_usb(device):
    #値が得られないとき、最大device.Retry回スキャンを繰り返す
    for i in range(device.Retry):
        try:
            sensorValue = GetOmronConnectModeData().get_env_usb_data(device.MacAddress)
        #エラー出たらログ出力
        except:
            logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
            sensorValue = None
            continue
        else:
            break
    
    #値取得できていたら、POSTするデータをdictに格納
    if sensorValue is not None:
        #POSTするデータ
        data = {        
            'DeviceName': device.DeviceName,        
            'Date_Master': masterdate,
            'Date': datetime.today(),
            'Temperature': sensorValue['Temperature'],
            'Humidity': sensorValue['Humidity'],
            'Light': sensorValue['Light'],
            'Pressure': sensorValue['Pressure'],
            'Noise': sensorValue['Noise'],
            'eTVOC': sensorValue['eTVOC'],
            'eCO2': sensorValue['eCO2']
        }
        return data
    #値取得できていなかったら、ログ出力してBluetoothアダプタ再起動
    else:
        logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
        restart_hci0(device.DeviceName)
        return None

######Inkbird IBS-TH1のデータ取得######
def getdata_ibsth1(device):
    #値が得られないとき、最大device.Retry回スキャンを繰り返す
    for i in range(device.Retry):
        try:
            sensorValue = GetIBSTH1Data().get_ibsth1_data(device.MacAddress, device.SensorType)
        #エラー出たらログ出力
        except:
            logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
            sensorValue = None
            continue
        else:
            break

    if sensorValue is not None:
        #POSTするデータ
        data = {        
            'DeviceName': device.DeviceName,        
            'Date_Master': masterdate,
            'Date': datetime.today(),
            'Temperature': sensorValue['Temperature'],
            'Humidity': sensorValue['Humidity']
        }
        return data
    #値取得できていなかったら、ログ出力してBluetoothアダプタ再起動
    else:
        logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
        restart_hci0(device.DeviceName)
        return None

######SwitchBot温湿度計のデータ取得######
def getdata_switchbot_thermo(device):
    #値が得られないとき、最大device.Retry回スキャンを繰り返す
    for i in range(device.Retry):
        #switchbotのセンサ値取得デリゲートを設定
        scanner = btle.Scanner().withDelegate(SwitchbotScanDelegate(str.lower(device.MacAddress)))
        #スキャンしてセンサ値取得
        try:
            scanner.scan(device.Timeout)
        #スキャンでエラーが出たらBluetoothアダプタ再起動
        except:
            restart_hci0(device.DeviceName)
        #値取得できたらループ終了
        if scanner.delegate.sensorValue is not None:
            break
        #値取得できなかったらログに書き込む
        else:
            logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
    
    #値取得できていたら、POSTするデータをdictに格納
    if scanner.delegate.sensorValue is not None:
        #POSTするデータ
        data = {        
            'DeviceName': device.DeviceName,
            'Date_Master': masterdate,
            'Date': datetime.today(),
            'Temperature': scanner.delegate.sensorValue['Temperature'],
            'Humidity': float(scanner.delegate.sensorValue['Humidity']),
            'BatteryVoltage': scanner.delegate.sensorValue['BatteryVoltage']
        }
        return data
    #取得できていなかったら、ログ出力してBluetoothアダプタ再起動
    else:
        logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}, timeout{device.Timeout}]')
        restart_hci0(device.DeviceName)
        return None

######Nature Remoのデータ取得######
def getdata_remo(device, csvpath):
    #センサデータ値が得られないとき、最大device.Retry回スキャンを繰り返す
    for i in range(device.Retry):
        try:
            sensorValue = GetRemoData().get_sensor_data(device.Token, device.API_URL)
        #エラー出たらログ出力
        except:
            logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, sensor]')
            sensorValue = None
            continue
        else:
            break
    #エアコンおよび電力データ値が得られないとき、最大device.Retry回スキャンを繰り返す
    for i in range(device.Retry):
        try:
            airconPowerValue = GetRemoData().get_aircon_power_data(device.Token, device.API_URL)
        #エラー出たらログ出力
        except:
            logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}, aircon]')
            sensorValue = None
            continue
        else:
            break
        
    #値取得できていたら、POSTするデータをdictに格納
    if sensorValue is not None:
        #センサデータ
        data = {        
            'DeviceName': device.DeviceName,        
            'Date_Master': masterdate,
            'Date': datetime.today(),
            'Temperature': sensorValue['Temperature'],
            'Humidity': float(sensorValue['Humidity']),
            'Light': sensorValue['Light'],
            'Human_last': sensorValue['Human_last'],
            'HumanMotion': GetRemoData().calc_human_motion(sensorValue['Human_last'], f'{csvpath}/{device.DeviceName}')
        }
        #エアコン＆電力データ
        if airconPowerValue is not None:
            data['TempSetting'] = int(airconPowerValue['TempSetting'])
            data['AirconMode'] = airconPowerValue['Mode']
            data['AirVolume'] = airconPowerValue['AirVolume']
            data['AirDirection'] = airconPowerValue['AirDirection']
            data['AirconPower'] = airconPowerValue['Power']
            if data['AirconPower'] == "":
                data['AirconPower'] = 'power-on_maybe'
            #電力
            if 'CumulativeEnergy' in airconPowerValue:
                data['CumulativeEnergy'] = float(airconPowerValue['CumulativeEnergy'])
            if 'Watt' in airconPowerValue:
                data['Watt'] = int(airconPowerValue['Watt'])
        return data
    #取得できていなかったら、ログ出力（WiFi経由なのでBluetoothアダプタ再起動はしない）
    else:
        logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
        return None

######データのCSV出力######
def output_csv(data, csvpath):
    dvname = data['DeviceName']
    monthstr = masterdate.strftime('%Y%m')
    #出力先フォルダ名
    outdir = f'{csvpath}/{dvname}/{masterdate.year}'
    #出力先フォルダが存在しないとき、新規作成
    os.makedirs(outdir, exist_ok=True)
    #出力ファイルのパス
    outpath = f'{outdir}/{dvname}_{monthstr}.csv'
    
    #出力ファイル存在しないとき、新たに作成
    if not os.path.exists(outpath):        
        with open(outpath, 'w') as f:
            writer = csv.DictWriter(f, data.keys())
            writer.writeheader()
            writer.writerow(data)
    #出力ファイル存在するとき、1行追加
    else:
        with open(outpath, 'a') as f:
            writer = csv.DictWriter(f, data.keys())
            writer.writerow(data)

######Googleスプレッドシートにアップロードする処理######
def output_spreadsheet(all_values_dict_str):
    #APIのURL
    url = 'GAS APIのURLをここに記載'
    #APIにデータをPOST
    response = requests.post(url, json=all_values_dict_str)
    print(response.text)

######Bluetoothアダプタ再起動######
def restart_hci0(devicename):
    passwd = 'RaspberryPiパスワードを入力'#必要に応じて隠蔽処理を加えてください
    subprocess.run(('sudo','-S','hciconfig','hci0','down'), input=passwd, check=True)
    subprocess.run(('sudo','-S','hciconfig','hci0','up'), input=passwd, check=True)
    logging.error(f'restart bluetooth adapter [date{str(masterdate)}, device{devicename}]')


######メイン######
if __name__ == '__main__':
    #開始時刻を取得
    startdate = datetime.today()
    #開始時刻を分単位で丸める
    masterdate = startdate.replace(second=0, microsecond=0)   
    if startdate.second >= 30:
        masterdate += timedelta(minutes=1)

    #設定ファイルとデバイスリスト読込
    cfg = configparser.ConfigParser()
    cfg.read('./config.ini', encoding='utf-8')
    df_devicelist = pd.read_csv('./DeviceList.csv')
    #全センサ数とデータ取得成功数
    sensor_num = len(df_devicelist)
    success_num = 0

    #ログの初期化
    logname = f"/sensorlog_{str(masterdate.strftime('%y%m%d'))}.log"
    logging.basicConfig(filename=cfg['Path']['LogOutput'] + logname, level=logging.INFO)

    #取得した全データ保持用dict
    all_values_dict = None
    #上記dictの文字列バージョン（GAS Post用、datetime型がJSON化できないため）
    all_values_dict_str = None

    #データ取得開始時刻
    scan_start_date = datetime.today()

    ######デバイスごとにデータ取得######
    for device in df_devicelist.itertuples():
        #Omron環境センサBAG型（BroadCast接続）
        if device.SensorType in ['Omron_BAG_EP','Omron_BAG_IM']:
            data = getdata_omron_bag(device)
        #Omron環境センサUSB型（Connectモード接続）
        elif device.SensorType in ['Omron_USB_EP','Omron_USB_IM']:
            data = getdata_omron_usb(device)
        #Inkbird IBS-TH1
        elif device.SensorType in ['Inkbird_IBSTH1mini','Inkbird_IBSTH1']:
            data = getdata_ibsth1(device)
        #SwitchBot温湿度計
        elif device.SensorType == 'SwitchBot_Thermo':
            data = getdata_switchbot_thermo(device)
        #remo
        elif device.SensorType == 'Nature_Remo':
            data = getdata_remo(device, cfg['Path']['CSVOutput'])
        #mesh
        elif device.SensorType == 'Sony_MeshHuman':
            data = getdata_mesh_human(device)
        #上記以外
        else:
            data = None        

        #データが存在するとき、全データ保持用Dictに追加し、CSV出力
        if data is not None:
            #all_values_dictがNoneのとき、新たに辞書を作成
            if all_values_dict is None:
                #all_values_dictを作成（最初なのでDate_MasterとDate_ScanStartも追加）
                all_values_dict = {'Date_Master':data['Date_Master'], 'Date_ScanStart':scan_start_date}
                all_values_dict.update(dict([(data['DeviceName']+'_'+k, v) for k,v in data.items() if k != 'Date_Master']))
                #dataを文字列変換してall_values_dict_strを作成(最初なのでDate_ScanStartを追加)
                data_str = dict([(k, str(v)) for k,v in data.items()])
                data_str['Date_ScanStart'] = str(scan_start_date)
                all_values_dict_str = {data_str['DeviceName']: data_str}
            #all_values_dictがNoneでないとき、既存の辞書に追加
            else:
                #all_values_dictに追加（最初でないのでDate_Masterは除外）
                all_values_dict.update(dict([(data['DeviceName']+'_'+k, v) for k,v in data.items() if k != 'Date_Master']))
                #all_values_dict_strに追加
                data_str = dict([(k, str(v)) for k,v in data.items()])
                all_values_dict_str[data_str['DeviceName']] = data_str

            #CSV出力
            output_csv(data_str, cfg['Path']['CSVOutput'])
            #成功数プラス
            success_num+=1

    ######Googleスプレッドシートにアップロードする処理######
    output_spreadsheet(all_values_dict_str)

    #処理終了をログ出力
    logging.info(f'[masterdate{str(masterdate)} startdate{str(startdate)} enddate{str(datetime.today())} success{str(success_num)}/{str(sensor_num)}]')
```
Remo E liteデータを取得可能にしたこと以外にも多少の改良を加えていますが、
実動作上は気にしなくとも良いレベルだと思います。

また、設定ファイルDeviceList.csvは、NatureRemoのもの（API_URLとTokenの記載が必要）が記載されていれば、
Remo E lite用に新たに追記する必要はありません。

cronでロギングを開始すると、下記赤枠のように、
`デバイス名_CumulativeEnergy : 総消費電力量(単位:kWh)`
`デバイス名_Watt : 現在の消費電力(単位:Watt)`
という2種類のフィールドが追加されます。
![spreadsheet_remoelite.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/d63c94bc-32a8-0379-c088-8ea0532ff9d6.png)

エアコンをONにすると消費電力が一気に増えていることが分かります。
次章で、この様子をグラフにしてみましょう。

##4. 消費電力ダッシュボードの作成
[Googleデータポータル](https://datastudio.google.com/navigation/)とは、クラウド上で編集、閲覧できるダッシュボードです。
このツールを使って、消費電力とエアコンOn-Offの関係を表すダッシュボードを作ります。

###新たなレポートの作成
![1_dataportal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/ac604cfb-35d9-2939-ebce-347c2ba52fdf.png)
**データ接続先に、Googleスプレッドシートを指定します**
![2_connect_spreadsheet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/af3fc5c6-f8d7-06af-842f-9b705a8b026f.png)
**承認を求められた場合、承認ボタンを押します**
![3_allow_spreadsheet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/490dbd9e-2270-7497-e730-456940e86a79.png)
**参照先のシートを求められるので④で作ったスプレッドシートを指定します**
![4_add_spreadsheet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/e8e85511-b8a3-89cd-e40f-cc67988002f8.png)
**「レポートに追加」を押します**
![5_confirm_spreadsheet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/5cb65a45-2e36-ee8b-24ca-bd0af9ad625a.png)
**レポート名を変更します**
![rename.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/31930513-8803-bb37-f84a-e89ea6da05f4.png)

###日ごと消費電力量グラフの作成
長期的な変化を見るグラフを作成します

**「リソース」→「追加済みのデータソースの管理」をクリックします**
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/c070c07d-97db-0754-a4cf-37aca017f8b2.png)

**対象のデータソースの「編集」をクリックします**
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/524205e2-5326-362c-6e66-b425d4ea3c97.png)

**日時を、認識できる形式に変更します**（データポータルは日時の認識フォーマットが厳しいです）
平均などの統計値を正しく出したい場合：YYYYMMDD
1行ごとの計測値を表示させたい場合：YYYYMMDDhhmm
![9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/28f02601-761b-c1b4-c32c-47f83037fb0b.png)

**フィールドを追加します(消費電力量計算用フィールド)**
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/3dcda25d-5728-efa9-8fc3-ce1ccf2d913e.png)

**下図のようにフィールドを設定します(消費電力量最大値-最小値≒その日の消費電力量)**
※(Count+1)/Countを掛けているのは、その日の一番最初の消費電力量が加算できていないため、その分を近似的に加えています
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/df5ad856-8d4c-bc26-fc08-3961594c41f6.png)

**メイン画面に戻ってグラフをクリックし、時系列グラフに変更します**
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/e07bf514-71c8-e45a-6f2d-9ca1fc522249.png)

**ディメンジョン（横軸=Date_Master_Day）と指標（縦軸=上で作成したDiff_CumlativeEnergy）を指定します**
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/3221e51b-8aa0-1a37-5c75-757d358a690f.png)

**指標名を"一日の消費電力量(kWh)に変更します"**
![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/f3c8e15f-6c86-6565-d04b-3a76b13bc3e0.png)

**欠損値を表示しないよう変更します**
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/722fe750-2a32-fbef-0cef-640b224168b3.png)

###瞬時消費電力＆エアコン比較グラフの作成
5分ごとに測定した消費電力(Watt)と、エアコンOn-Offを比較するグラフを作成します
####瞬時消費電力グラフの作成

![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/16e1b875-953c-a325-c139-5817851917c4.png)

**ディメンジョン（横軸=Date_Master）と指標（縦軸=デバイス名_Watt）を指定します**
![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/e9c79474-72b4-d072-4561-ca4db85ea110.png)

**表示する日付範囲を指定します(下図では2日前0時～当日24時まで)**
![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/755fff26-f40e-226e-7d56-67b740783615.png)

####エアコンOn-Off情報の追加

**「リソース」→「追加済みのデータソースの管理」をクリックします**
![7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/c070c07d-97db-0754-a4cf-37aca017f8b2.png)

**対象のデータソースの「編集」をクリックします**
![8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/524205e2-5326-362c-6e66-b425d4ea3c97.png)

**エアコンのOn-Offを表すフィールド"Aircon_On"を、下図のように作成します**
![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/2cb20ef8-b81f-e2cd-3bd0-68a780e5ecd3.png)

**Aircon_On"を、瞬時消費電力と同じグラフに追加します**
![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/d51b4f08-2b0f-2e45-42e7-4283ee5c806a.png)

**欠損値を線形補完するよう変更します**
![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/99ee2d11-7e86-8ded-d06a-cba2232cbd0b.png)

**グラフの表示色等を調整します**
![20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/bb983581-642a-06e6-d851-4c15f548056c.png)

###グラフ全体の調整
全体のレイアウトを、「テーマとレイアウト」から調整します
![21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/7cc6fcfd-b071-6a17-fa61-3b4ce932367b.png)
お好みに合わせて、グラフを追加 → スコアカードで必要な数値を表示させると、
見栄えが良くなります

※「現在の消費電力」の表示には、スプレッドシート中「Current」シートのデータを追加する必要があります

**以上で完成です！**
![watt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/9bed27c2-eb03-c5e8-6735-582354eb6d42.png)

#おわりに
見ての通り、エアコンの消費電力が凄まじいことが分かりました（ONにすると消費電力が倍以上に‥）
また、ONにした直後が最も消費電力が大きく、徐々に消費電力が下がって一定のレベルまで下がるとサチる、という動きをするようです。

**TVや電球のON-OFF情報**もAPIで取得できそうなので、
これらを加えた上で消費電力との関係を見ていこうと思います。

見える化で終わらせずに電気代を削減して「価値」を生み出したいですね！
めざせ！**省エネハウス！**