うごくものをつくる

個人的な学習ノート

基礎技術メモ

ESP32とAmbientサーバーとの連携 ~データの送信と受信~

ESP32やRaspberry Piのようなインターネットに繋がるマイコンやシングルボードコンピュータは、データを測定してそれをネット上のサーバーにアップロードしたり、逆にサーバーから情報を受信してそれに応じてハードウェア制御をしたりといった、IoT (Internet of Things)用途に使うことができる。

使うことができると言うよりも、もはやESP32なんかはそのためのマイコンと言っても過言ではない。

というわけで、ESP32が何らかのデータをクラウドにアップロード&ダウンロードするための方法について学んだので本記事にまとめておく。

1. Ambientにユーザー登録

データの受け渡し先はAmbientを使用することにする。

AmbientはIoTデータの可視化サービスです。

マイコンなどから送られるセンサデータを受信し、蓄積し、可視化(グラフ化)します。

ambidata.io

ESP32をArduinoで動かすのであれば、Ambientのライブラリがあるので扱いは簡単。

ログインするとこんな画面がある。必要なのはチャネルID、リードキー、ライトキーの3つ。チャネル名には最初は適当な文字が入っている(変更可能)。

2. ESP32でAmbientにデータ送信

ESP32でAmbientのサービスを使うのは、ライブラリを使うと楽だ。まずはArduino IDEにAmbientのライブラリをインストールしておく。Arduino IDEの「ツール」→「ライブラリを管理…」を選択し、ライブラリマネージャを立ち上げる。「ambient」と検索すると「Ambient ESP32 ESP8266 lib」というのが出てくるので、これをインストールする。

とりあえずこれを使って、ボタンを押したときに、電源を入れてからボタンを押した合計回数をAmbientに送信するプログラムを組んでみる。

#include <Arduino.h>
#include "Ambient.h"

WiFiClient client;
Ambient ambient;

uint8_t cnt = 0;//ボタンを押した回数格納用変数
const char* ssid = "...ssid...";
const char* password = "...password...";

unsigned int channelId = *****; // AmbientのチャネルID
const char* writeKey = "...writeKey..."; // ライトキー

void dataSend(uint8_t a)
{
    ambient.set(1, a); // 変数aをデータ1にセット
    ambient.send(); // データをAmbientに送信
}

void setup() {
  Serial.begin(115200);
    delay(10);
    Serial.println("Start");

    WiFi.begin(ssid, password);  //  Wi-Fi APに接続
    while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
        delay(100);
    }

    Serial.print("WiFi connected\r\nIP address: ");
    Serial.println(WiFi.localIP());

    ambient.begin(channelId, writeKey, &client); // チャネルIDとライトキーを指定してAmbientの初期化
	pinMode(5, INPUT_PULLUP);
}

void loop() {
  if(digitalRead(5) == LOW)
  {
    cnt += 1;
    Serial.print("押した回数は、");
    Serial.println(cnt);
    dataSend(cnt);
    delay(10000);
  }
  
}

Wi-FiのSSIDとパスワード、AmbientのチャネルIDとライトキーは自分のものを入れる。

ESP32の5番ピンをインプットとして設定し、タクトスイッチでGNDと繋ぐ(押したときにピン5とGNDが繋がるようにする)。ボタンが押されたら、押したトータル回数をサーバーにアップロードする。また、Ambientはデータを送信したら次のデータを送るまで5秒は待たないといけないルールになっているので、送信後は余裕を持って10秒間処理を停止する。これはただの動作確認用プログラムなので、押しっぱなしには特に対処していない。

これをマイコンに書き込んで実行し、Ambientのページからチャネルを開いてみるとデータがグラフになって表示される。

押すたびに回数は増加していくので、こんな感じで右肩上がりのグラフができている。(最初の方のデータで1が連続しているのは、何度か動作テストで再起動させてカウントがリセットされたため)

ちなみに、Ambientはひとつのチャネルごとにデータを8種類格納でき、設定画面からそれぞれに任意の名前をつけることもできる。今回の送信テストではボタンを押した回数という1種類のデータしかないので、データ1のみを用いた。データ1には「1号機」という名前を設定した。

送信はこれでできていることが確認できたので、次は受信のやり方を記す。

3. Ambientからのデータ受信

データ送信のときはライブラリ内にそういう機能があってわかりやすかったけど、受信の場合は送信のときとはあまり似ていないやり方をする必要がある。

データの受信時は、下記のURLにアクセスすることで、サーバーから値が返ってくるのを読み取る。その形式はJSON形式である。

http://ambidata.io/api/v2/channels/チャネルID/data?&readKey=リードキー&n=行数

チャネルID、リードキーは自分のものを入れる。行数というのは、1としておくと直近のデータ1個のみ返ってくる。2以上の数値を入れると、その数だけ過去にさかのぼって複数のデータを受信する。

今回はとりあえず最新のデータ1個のみあればよいので、行数は1とする。

このスイッチを押した時、このURLにアクセスするプログラムは次のようになる。

#include <Arduino.h>
#include <HTTPClient.h>
#include "Ambient.h"

WiFiClient client;
Ambient ambient;
const char* ssid = "...ssid...";
const char* password = "...password...";

unsigned int channelId = 	*****; // AmbientのチャネルID
const char* readKey = "...readKey..."; // リードキー

void dataRead()
{
  String url = "http://ambidata.io/api/v2/channels/";
  url += channelId;
  url += "/data?&readKey=";
  url += readKey;
  url += "&n=1";

  HTTPClient httpClient;
  httpClient.begin(url);
  int httpCode = httpClient.GET();
  String httpResponse = httpClient.getString();
  httpClient.end();
  Serial.println(httpResponse);
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
    delay(10);

    Serial.println("Start");

    WiFi.begin(ssid, password);  //  Wi-Fi APに接続
    while (WiFi.status() != WL_CONNECTED) {  //  Wi-Fi AP接続待ち
        delay(100);
    }

    Serial.print("WiFi connected\r\nIP address: ");
    Serial.println(WiFi.localIP());

    pinMode(5, INPUT_PULLUP);
}

void loop() {
    if(digitalRead(5) == LOW)
  {
    dataRead();
    delay(5000);
  }
}

Ambientサーバーからの返答をString型変数で受け、これをシリアルモニタにそのまま表示するようにしている。なお、データ送信の際は5秒待つように規定されていたが、読み取りの際は待ち時間があるのかどうかわからなかった。しかし、あまり頻繁にサーバーに問い合わせても負担をかけるだけだし意味がないので、とりあえず5秒待つようにした。

これを実行してみると、シリアルモニタには次のような文字列が表示される。

[{“d1″:4,”created”:”2021-02-23T03:54:57.171Z”}]

これは、d1、すなわちデータ1の値が4であるというのを表している。先程のデータ送信テストの時のデータ1の値が4のときに読み取りを行ったので、このような結果となる。また、ここで用いたAmbientのチャネルにはデータ1以外のデータは無いので、d1のみが入っている。もし複数データが保存されていれば、d2、d3 … と続く。

最後に、データが最終更新された日時が記されている。

さて、これで一応受信もできているということにはなるんだけど、こういう文字列でデータが返ってきてしまうと、データ1の値をint型変数に格納して何らかの処理をして、データ2は別の変数に入れて……といった処理がやりづらい。

そこで、このJSON形式のデータを分解して扱いやすくする。

4. ArduinoJSONを使う

そのためには、ArduinoJSONというライブラリを使う。先程と同様にしてArduino IDEにArduinoJSONを追加しておく。

先程の読み取りテストプログラムの26行目から下、つまりdataRead()内で文字列を取得した後の部分に下のコードを追加する。dataRead()内の追加分のみ記載しているので下には載っていないが、プログラムの頭で#include <ArduinoJson.h>と書いてちゃんとライブラリをインクルードしておくこと。

//文字列の編集(大括弧を消す)
  int mojisu = httpResponse.length();
  httpResponse.setCharAt(0, ' ');//0番目(=1文字目)を半角スペースに置換
  httpResponse.setCharAt(mojisu-1, ' ');//最後の文字を半角スペースに置換
  Serial.println("Edited json is, ");
  Serial.println(httpResponse);

//JSONドキュメントを作成(サイズは実際のデータに合わせる。https://arduinojson.org/v6/assistant/)
  StaticJsonDocument<200> doc;

  // JSONドキュメントをパース
  DeserializationError error = deserializeJson(doc, httpResponse);

  // パース処理エラー時
  if (error) {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(error.f_str());
    return;
  }

  //データの抜き取り
  int data1 = doc["d1"];//d1に保存された値をint型変数data1に入れる
  Serial.print("data1 is ");
  Serial.println(data1);
  String savedTime = doc["created"];
  Serial.print("created time is ");
  Serial.println(savedTime);

実は、ArduinoJSONを使う前に下準備が要る。Ambientから返ってきた文字列をよく見ると、先頭と末尾に大括弧[ ]がある。これが入っているとArduinoJSONはうまく動作しないので、まずこれらを削除する必要がある。

最初の「文字列の編集」というコメントのところでやっているのがそれ。先頭と末尾の文字を半角スペースに置き換えている。これで大括弧は消える。その代わりに半角スペースが残っているが、動作確認してみたところ半角スペースがあっても動作には問題ないみたいなのでこのままにしておく。気持ち悪いならtrim()で消すこともできる。(参考:Arduino 日本語リファレンス

StaticJsonDocument<200>で、JSONドキュメントを作成する。< >の中はサイズを指定する数値。https://arduinojson.org/v6/assistant/にアクセスして実際に使うJSONデータを入力してみて、出てきたサイズの値から決める。大きめに余裕を持っておいたほうが良いかも。

あとは記載のとおりにやればよい。docの中からデータ1を取り出したいときは、データ1は”d1″というキーワードと紐付いているので、doc[“d1”]で呼び出すことができる。これをint型変数に入れてやれば、データ1の値を使って様々な処理をすることができるようになる。データ2, データ3……についても同様。

データの最終更新時刻は”created”というワードに紐付いている。

5. まとめ

・Ambientを使って、Arduino(ESP32)からデータを送ってサーバーに保存したり、サーバーに保存されているデータを読み出すことができた。

・ArduinoJSONを使って、Ambientから読み取ったJSONデータから必要なものを抜き取ることができた。

・Ambientが返すJSONデータには大括弧が含まれており、まずはこれを消さないとArduinoJSONで上手く値を抜き取れない(2021, 2/23時点)

2 コメント

  1. WiFiにつながりません。何が原因でしょうか。

    「Iot開発スタートブック ESP32でクラウドにつなげる電子工作をはじめよう!」の

    79ページ 「プログラム3-5:WiFi_test.ino」
    をスケッチして正常に書き込みができましたが、図3-22のような実行結果が表示されません
    プルグラムは、何回もチェックしました、ssid password も確認しました。

    何か事前に設定しなければならない事項がありますか、78ページまでに何か設定を見逃して
    いるのでしょうか?

    プログラムのなかで、//ローカルIPアドレスをプリントする。の行が動作が悪いのでしょうか、
    プログラムのなかで、//WIFiネットワークに接続する。の行が動作していないのでしょうか、

    点検、チェックすべき箇所がありましたら教えていただけませんか。

    環境:Windwos 11 arduino 1.8.13 ESP32(スイッチサイエンスから購入)

      

    • コメントありがとうございます。
      私はその本を持っていないので、どういうプログラムなのかも貴方のESP32にどのような症状が出てうまく行っていないのかも分からず、推測でのお答えになりますが……
      1. Wi-Fiルーターが5GHzのみオンになっている
      ESP32は2.4GHzしか対応していないので、今どきはもう5GHzの時代でしょって感じで無線LAN側で2.4GHzをオフにしてしまって5GHz電波しか出ていないと、ESP32では接続できないです。

      2. 電源が貧弱でESP32が落ちてる
      ケーブルの問題や給電機器側の問題で、Wi-Fiに接続しようとして多量の電流を必要としているESP32に対し十分な電力を供給できなかった場合、ESP32が落ちてしまい再起動→起動後再びWi-Fi接続しようとして落ち…のループになってしまいます。電源系統をパワフルなものに変えるというのも手です。

      3. 謎エラー
      私の作ったものでも、たまにWi-Fi接続成功するまでやたら時間かかることがあります。数分待てば繋がりますが……。
      そういうとき、しばらく待って「あれ?つながらないな?」と思ったら、ESP32ボード側のリセットボタン(ENボタン)を押して再起動させてやると、今度はサクッとすぐ繋がったりします。
      原因はよくわからないですがそういうこともあるので、ちょっと待ってから一度リセットかけてみてはどうでしょうか(既に試されているかもしれませんが…)

返信する

メールアドレスが公開されることはありません。 が付いている欄は必須項目です