前回までで、ポータブル電源自動充電システムに必要な EcoFlow IoT Developer Platform(開発者プラットフォーム)の署名生成の方法やデバイスリストの取得までを行いました。
今回はプログラム上から開発者プラットフォームを利用して、自宅にあるポータブル電源にアクセスします。
リアルタイムにポータブル電源のさまざまな情報を取得することが出来ます。
ちなみにポータブル電源自動充電システムのシステム構成とメリット・デメリットについては、以下の記事で紹介しています。
EcoFlow IoT Developer Platform(開発者プラットフォーム)とは
EcoFlow IoT Developer Platform(開発者プラットフォーム)はざっくり言うと、プログラム上からポータブル電源の情報を取得したり、ポータブル電源の設定を変更する操作を行えるようにするためのサービスです。
開発者プラットフォームを利用するためには、開発者になるための申請を行い、アクセスキーとシークレットキーを発行する必要があります。
まだの方は以下の記事をご覧ください。
開発者プラットフォームのAPIにリクエストするための署名の作り方については以下の記事で詳しく記載しています。
リアルタイムデバイス情報の取得
本記事で紹介するAPI(quotaAll)を利用することで、デバイス(ポータブル電源)の情報をリアルタイムに取得することが出来ます。
ポータブル電源のバッテリー残量や温度、入力電力量や出力電力量、充電中または放電中、など様々な情報が取得できます。
それらの情報を利用して、Googleスプレッドシート上にグラフ表示など(ダッシュボード)を行うことも出来ますし、自動充電プログラムの条件判断に利用したりも出来ます。
公式ドキュメント
まずは公式ドキュメントのページを確認してみましょう。
Query device’s all quota infomation
EcoFlow IoT Developer Platform – generalInfo
ポータブル電源のリアルタイム情報を取得するためのリクエストは、以下の仕様となっています。
情報を取得するポータブル電源を特定するためにsn(シリアルナンバー)をリクエストパラメータとして指定することになっています。
Key | Value |
Method | GET |
URL | https://${host}/iot-open/sign/device/quota/all |
Content-Type | none |
リクエストパラメータ
名称 | 型 | 必須 | 説明 |
sn | String | ◯ | シリアルナンバー |
サンプルコードは以下となっていますが、こちらはコマンドで実行する場合のサンプルとなっています。
(Google Apps Script(GAS)で利用する場合のコードは後述します)
// Get Delta Pro all quota values
// Request
curl -X GET https://api-e.ecoflow.com/iot-open/sign/device/quota/all?sn=DCABZ*** \
-H 'accessKey:OCHzRuj6NLF7o43***' \
-H 'timestamp:1681872798000' \
-H 'nonce:238752' \
-H 'sign:534215ccfe93979588975630ea0ad7091c7e250ae5***'
// Response
{
"code":"0",
"message":"Success",
"data":{
"bmsMaster.soc":"100",
"bmsMaster.temp":"34",
"bmsMaster.inputWatts":"0",
"bmsMaster.outputWatts":"0",
"pd.remainTime":"14781",
"inv.cfgAcEnabled":"0",
"mppt.carState":"0",
"pd.usb1Watts":"0",
"pd.usb2Watts":"0",
"pd.qcUsb1Watts":"0",
...
}
}
Google Apps Script(GAS)でコーディング
では実際にコードを書いてみましょう。
最終的なコードは以下になります。
function quotaAll() {
// ① リクエストURL
const host = "https://api-e.ecoflow.com";
const requestUrl = host + "/iot-open/sign/device/quota/all";
// ② アクセスキー、シークレットキーとシリアルナンバー
const accessKey = "※開発者プラットフォームで発行したアクセスキー";
const secretKey = "※開発者プラットフォームで発行したシークレットキー";
const serialNo = "※ポータブル電源のシリアルナンバー";
// ③ nonceとタイムスタンプを生成
let nonce = String(Math.floor(Math.random() * 900000) + 100000);
let timestamp = String((new Date).getTime());
// ④ リクエストパラメータ
let requestParams = "sn=" + serialNo;
// ⑤ 署名用パラメータ
let signParam = requestParams + "&accessKey=" + accessKey + "&nonce=" + nonce + "×tamp=" + timestamp;
// ⑥ 暗号化して16進文字列に変換
let signature = Utilities.computeHmacSha256Signature(signParam, secretKey);
let sign = signature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
// ⑦ リクエストヘッダー
let requestHeaders = {
'accessKey': accessKey
,'timestamp': timestamp
,'nonce': nonce
,'sign':sign
}
// ⑧ リクエストオプション
let requestOptions = {
"method" : "GET",
"headers" : requestHeaders,
"muteHttpExceptions" : true
}
// ⑨ API実行(開発者プラットフォームへのリクエスト)
let response = UrlFetchApp.fetch(requestUrl + "?" + requestParams, requestOptions);
// ⑩ 結果(レスポンス)を取得
let responseCode = response.getResponseCode();
let responseText = JSON.stringify(JSON.parse(response.getContentText()), null, 2);
// ⑪ ログ出力
Logger.log("responseCode=" + responseCode);
// ⑫ ファイルに保存
let output_file = Utilities.newBlob("", "text/plain", "quotaAll").setDataFromString(responseText, "UTF-8");
DriveApp.createFile(output_file);
}
コードの解説
コードの内容をそれぞれ解説していきます。
// ① リクエストURL
const host = "https://api-e.ecoflow.com";
const requestUrl = host + "/iot-open/sign/device/quota/all";
EcoFlow IoT Developer Platform(開発者プラットフォーム)にリクエストするためのURLを定義しています。
開発者プラットフォーム(API)のホスト名はhttps://api-e.ecoflow.comです。
そこに今回取得したいデバイス情報取得のURL:/iot-open/sign/device/quota/allを組み合わせて、最終的なURLを作っています。
https://api-e.ecoflow.com/iot-open/sign/device/quota/all
// ② アクセスキー、シークレットキーとシリアルナンバー
const accessKey = "※開発者プラットフォームで発行したアクセスキー";
const secretKey = "※開発者プラットフォームで発行したシークレットキー";
const serialNo = "※ポータブル電源のシリアルナンバー";
EcoFlow IoT Developer Platform(開発者プラットフォーム)から発行した、ご自身のアクセスキーとシークレットキー および ポータブル電源のシリアルナンバーをここに設定してください。
今回はわかりやすいようにアクセスキーとシークレットキー、シリアルナンバーを直接ソースコードに記載していますが、本来はセキュリティを考慮し、ソースコードと別で管理することをお勧めします。
Google Apps Script(GAS)のプロパティサービス等の利用を検討してください。
まだアクセスキーとシークレットキーを発行していないようであれば、以下の記事を参考に発行してください。
ポータブル電源のシリアルナンバーはスマホアプリから確認するか、開発者プラットフォームからデバイスリストを取得することで確認ができます。
// ③ nonceとタイムスタンプを生成
let nonce = String(Math.floor(Math.random() * 900000) + 100000);
let timestamp = String((new Date).getTime());
③ではnonceとtimestampの作成を行っています。
順に見ていきましょう。
- Code1Math.random()
Math.random()では 0~1未満 の乱数が生成されます。
- Code2Math.floor(Math.random() * 900000)
上記Code1で採番した乱数(0~1未満)に 900000 を掛けることで、0~900000未満 の値となります。
Math.floor()では小数点以下を切り捨てています。
結果手として、ここでは 0~899999 の乱数が作り出されます。 - Code3Math.floor(Math.random() * 900000) + 100000
上記Code2で出来た 0~899999 の乱数に対して、100000を足しています。
これで値としては 100000~999999 となり nonceとして欲しい6桁の整数となります。 - Code4String(Math.floor(Math.random() * 900000) + 100000)
最後にString()で文字列に変換してnonceは完成です。
nonceの採番(生成)方法については、必ずしも上記の実装である必要はありません。
ようは6桁数整数文字列が作成できれば良いので、他のやり方でも構いません。
- Code1new Date
システム日時(現在日時)で日付オブジェクトを作成しています。
- Code2(new Date).getTime()
上記Code1で作成した日付オブジェクトに対してgetTime()することで、システム日時(現在日時)のミリ秒を取得しています。
- Code3String((new Date).getTime())
最後にString()で文字列に変換してtimestampは完成です。
- Qnonceとは?
- A
nonceとは、ノンス/ナンスと呼ばれます。
API呼び出し時に一度だけ有効な任意の値(乱数)になります。
開発者プラットフォームではnonceとして 6桁の整数文字列 が必要です。
セキュリティ保護のためにAPIにリスエストする都度、新たな乱数を採番します。
- Qtimestampとは?
- A
名前の通りタイムスタンプ(現在日時)になります。
1970年1月1日00:00:00UTCからの経過ミリ秒数を設定します。
// ④ リクエストパラメータ
let requestParams = "sn=" + serialNo;
④ではデバイス情報取得API(quotaAll)へのリクエスト用のパラメータ文字列を作成しています。
デバイス情報取得API(quotaAll)にはポータブル電源のシリアルナンバーを渡す必要があります。
(どのポータブル電源の情報を取得するのかを指定しないといけないので当然ですね)
単純にイコールで繋げて sn=xxxxxxxxxxx といった文字列を作成しています。
// ⑤ 署名用パラメータ
let signParam = requestParams + "&accessKey=" + accessKey + "&nonce=" + nonce + "×tamp=" + timestamp;
⑤では署名生成用のパラメータ文字列を作成しています。
④の手順で作成したリクエストパラメータに、署名用のパラメータとしてはお決まり(固定)の以下パラメータを単純に文字列として連結しています。
- アクセスキー(accesskey)
- ナンス/ノンス(nonce)
- タイムスタンプ(timestamp)
// ⑥ 暗号化して16進文字列に変換
let signature = Utilities.computeHmacSha256Signature(signParam, secretKey);
let sign = signature.reduce(function(str,chr){
chr = (chr < 0 ? chr + 256 : chr).toString(16);
return str + (chr.length==1?'0':'') + chr;
},'');
ここでは署名用パラメータを暗号化して、署名(署名値)を作成しています。
以前の記事でも記載していますが、この部分の実装は難解ですので、一旦はお決まりとして「こういうものなんだなー」と思っていただければOKだと思います。
// ⑦ リクエストヘッダー
let requestHeaders = {
'accessKey': accessKey
,'timestamp': timestamp
,'nonce': nonce
,'sign':sign
}
EcoFlow IoT Developer Platform(開発者プラットフォーム)にリクエストするためのHTTPヘッダーを作成しています。
開発者プラットフォームではHTTPヘッダーに以下を設定してAPIを呼び出すルール(仕様)になっています。
- アクセスキー(accesskey)
- タイムスタンプ(timestamp)
- ナンス/ノンス(nonce)
- 署名(sign)
// ⑧ リクエストオプション
let requestOptions = {
"method" : "GET",
"headers" : requestHeaders,
"muteHttpExceptions" : true
}
こちらはリクエストのオプション設定になります。
オプションとして必須なのは以下のみです。
- method
デバイス情報の取得リクエストではGETを指定するルール(仕様)です。 - headers
⑦で作成したHTTPヘッダーを指定します。
他、以下の指定はおまけ程度です(とりあえず無くても動くと思います)
- muteHttpExceptions
trueを指定することで、HTTPリクエスト時のエラー(例外)を抑制するオプションです。
※すべての例外を抑制できるわけではないという事と、例外が発生しないだけでエラー処理が不要になる訳では無い(本来は適切にエラー処理が必要)というところは注意が必要です。
// ⑨ API実行(開発者プラットフォームへのリクエスト)
let response = UrlFetchApp.fetch(requestUrl + "?" + requestParams, requestOptions);
ここでは実際にAPI(開発者プラットフォームへのリクエスト)の実行を行っています。
ここまでで作成したリクエストURLとリクエスト用のオプションを指定して、UrlFetchApp.fetch()を呼び出すことで、実際の通信が行われ、結果のレスポンスがresponse(変数)に格納されます。
// ⑩ 結果(レスポンス)を取得
let responseCode = response.getResponseCode();
let responseText = JSON.stringify(JSON.parse(response.getContentText()), null, 2);
デバイスリストの取得結果(レスポンス)にはHTTPステータスとデバイスのリアルタイム情報が格納されています。
ここではHTTPステータスと取得したデバイス情報をそれぞれ変数に格納しています。
おまけ程度ですがデバイス情報については、JSON.parse()、JSON.stringify()、を介すことで整形された見やすい形の文字列に変換しています。
// ⑪ ログ出力
Logger.log("responseCode=" + responseCode);
結果のHTTPステータスをログに出力しています。
今回はデバイス情報については情報量が多いためログには出力せず、次の手順でファイルに出力しています。
// ⑫ ファイルに保存
let output_file = Utilities.newBlob("", "text/plain", "quotaAll").setDataFromString(responseText, "UTF-8");
DriveApp.createFile(output_file);
取得結果のデバイス情報をファイルに出力しています。
上記のソースが実行されるとマイドライブ内にquotaAllという名前のテキストファイルが作成され、その中にポータブル電源のリアルタイム情報が出力されます。
コード実行
コーディングを行ったら、変更を保存(フロッピーのアイコンをクリック)してから実行してみてください。
以下のような結果がレスポンスとして返ってくると思います。
// quotaAll(テキストファイル)
{
"code": "0",
"message": "Success",
"data": {
"bms_slave_bmsSlaveStatus_2.f32ShowSoc": 34.5,
"mppt.faultCode": 0,
"bms_slave_bmsSlaveInfo_1.highTempChgTime": 0,
"bms_slave_bmsSlaveStatus_1.soh": 100,
"bms_emsStatus.maxChargeSoc": 97,
"bms_slave_bmsSlaveStatus_1.targetSoc": 31.1,
"bms_bmsInfo.bsmCycles": 45,
"bms_bmsStatus.designCap": 40000,
"bms_slave_bmsSlaveStatus_2.realSoh": 0,
"pd.bpPowerSoc": 80,
"bms_bmsStatus.outputWatts": 0,
...
"pd.invOutWatts": 56,
"bms_slave_bmsSlaveStatus_2.fullCap": 39280,
"bms_bmsStatus.chgCap": 4294967295,
"bms_emsStatus.dsgRemainTime": 5999,
"inv.sysVer": 33554509
},
"eagleEyeTraceId": "****************************",
"tid": ""
}
ポータブル電源にエクストラバッテリー(拡張バッテリー)を接続している場合には、エクストラバッテリー(拡張バッテリー)のリアルタイム情報も含まれた形で取得できます。
- Q実行時に「認証が必要です」「このアプリはGoogleで確認されていません」といったセキュティの警告が表示される場合は?
- A
Google Apps Script(GAS)では、プログラムの初回実行時などに「認証が必要です」や「このアプリはGoogleで確認されていません」といったセキュリティの警告が出ることがあります。
今回のソースコードでは、外部サービス(EcoFlow開発者プラットフォーム)に接続しようとしているため、その外部サービスに接続して本当に大丈夫ですか?安全ですか?とGoogleから確認されている状態です。
あなた自身がソースコードを理解し、外部サービス(EcoFlow開発者プラットフォーム)に接続して問題ないと判断したのであれば以下の手順に従って、プログラムの実行を承認(許可)してください。
脅すわけではありませんが、これはセキュリティ警告です。
記事に記載してあるソースコードを鵜呑みにせず、必ずご自身で理解し、問題ないことを確認の上で『承認(許可)』の操作をお願いします。
まとめ
お疲れ様でした。
これでプログラム上から任意のタイミングでポータブル電源のリアルタイム情報を取得することが出来るようになりました。
Google Apps Script(GAS)で定期的にポータブル電源の情報を取得するようにすれば、スプレッドシート上で以下のようなダッシュボード(グラフなど)を表示し、ポータブル電源の状態を可視化して確認することも出来るようになります。
もちろん本記事のメインであるポータブル電源自動充電システムの充電制御をするための情報としても利用しています。
次回はEcoFlow IoT Developer Platform(開発者プラットフォーム)を利用して、ポータブル電源の設定を変更する方法を記載していければと思っています。
ついにプログラム上から充電のON/OFFや充電速度の変更が行えるようになりますよ。