eyecatch

たまたま、Arduinoと温湿度気圧センサーのセットを触る機会があったので、それの応用としてSwitchbot側と連動させようとしたことがきっかけです。いくつかハマったポイントがあったので、サンプルコードをベースに記しておきたいと思います。コードは、loop関数あるいは元関数から叩く部分、さらにAPIを実際にアクセスする部分の2つに分かれます。また、ライブラリはArduinoJSON、WiFiClientSecureのみです。

float GET_SW_data(String data_type) { StaticJsonDocument<1090> device_data, meeter_data; static String meeter_id; static float temperature, humidity, Last_access_Time; if(Last_access_Time == 0){ // 初回アクセスのみURL同定のためデバイスリスト取得 device_data = switchbot_API_ACCESS("https://api.switch-bot.com/v1.0/devices"); meeter_id = device_data["body"]["deviceList"][0]["deviceId"].as<String>(); goto GET_meeter_data; }else if((millis() - Last_access_Time) > 120000){ // 周期設定 120000[ms] = 120[s] = 2[min] goto GET_meeter_data; }else{ Serial.println(F("\nREAD switchbot data from cache--------------------\n")); goto RETURN_data; } GET_meeter_data: { setupNTPsetting(); Last_access_Time = millis(); Serial.println(F("\nREAD data from switchbot API--------------------\n")); meeter_data = switchbot_API_ACCESS("https://api.switch-bot.com/v1.0/devices/" + meeter_id + "/status"); temperature = meeter_data[F("body")][F("temperature")]; humidity = meeter_data[F("body")][F("humidity")]; } RETURN_data: { if(data_type == F("Temprature")){ return temperature; }else if(data_type == F("Humidity")){ return humidity; }else{ return 0; } } }

ここが他の関数と繋ぐ部分です。引数は、返す値を確定させるために用いています。

なぜ必要なのかというと、 APIのアクセス回数が一日に1000と規定されているためです。そのため、初回のみデバイスリストを取得し、一定周期で他の情報を取得するようにしています。

従前の回数制限回避には、もうひと工夫あります。取得したJSONを、変数として保持する挙動を発見できなかったため、そこからクエリで起こしたものを、スタティック変数で格納しています。システムとしては、呼び出し元でもスタティック変数に格納しているので、余分なAPIアクセスが発生しません。

JsonObject switchbot_API_ACCESS(String base_URI){ JsonObject doc; WiFiClientSecure client; client.setInsecure(); if(!client.connect(PSTR("api.switch-bot.com"), 443)){ Serial.println(F("connection failed--------------------\n")); }else{ String payload; String str1 = F("GET "); str1 += String(base_URI); str1 += F(" HTTP/1.1\r\nHost: api.switch-bot.com\r\nAuthorization: ==AuthorizationSecret==\r\nConnection: close\r\n\r\n\0"); //closeを使うと、サーバーの応答後に切断される。最後に空行必要 client.print(str1); //client.println にしないこと。最後に改行コードをプラスして送ってしまう為 client.flush(); //client出力が終わるまで待つ // ヘッダの受信 Serial.println(F("GET Request Send--------------------\n")); while(1){ String line = client.readStringUntil(PSTR('\n')); if(line == F("\r")){ break; } // ヘッダの末尾は\r\nだから終了 Serial.println(line); } // ボディの表示 while(client.available()){ payload += client.readStringUntil('\r'); } //サーバーから送られてきた文字を1文字も余さず受信し切ることが大事 delay(10); client.stop(); //特に重要。コネクションが終わったら必ず stop() しておかないとヒープメモリを食い尽くしてしまう。 delay(10); Serial.println(F("Client Stop--------------------\n")); DynamicJsonDocument jsonBuffer(1090); DeserializationError error = deserializeJson(jsonBuffer, payload); if(error){ Serial.print(F("deserializeJson() failed: ")); Serial.println(error.f_str()); }else{ doc = jsonBuffer.as<JsonObject>(); } } return doc; }

==AuthorizationSecret==は各自のものに読みかえてください。また、JSONを展開するところための、中間変数がメモリを静的に確保しているので、そこも環境に応じてです。 シリアルモニターを見ながら、オーバーフローしないだけの場所を確保してください。

まず、他のAPI連携例と違うところは、https接続が必須であるのに、インセキュアで 接続していることです。本来、公開証明書をセットして、コネクションを確立するところです。実挙動ではそれがコネクションエラー、 セットせずにインセキュアとすると、コネクション確立となります。これは、ヘッダー情報も使って認証をかけているためだと思われます。

以上が、今回手探りで組みあげたコードになります。ここから、最適なアクセス周期設定、JSONのボディ部分を取り出すフィルター実装、値を返す部分の最適化等を目指していきます。