LoginSignup
63
60

More than 3 years have passed since last update.

M5Stick-CでJsonをPOSTする

Last updated at Posted at 2019-07-15

M5Stick-Cを使って、HTTP POSTします。当然、BodyにJSONを入れて、JSONが返ってくるやつです。
今回は、Arduino IDEを使います。
いろんな人が使っているので情報がたくさんありますが、JsonをPOSTするまで複数の情報を集めないと実現できなかったので、ここでまとめておきます。

使うもの
・M5Stack-C
  ESP系CPUであれば大丈夫なのですが、M5Stack-CについているLCDを使っているのでそこだけ代替してください。
・Arduino IDE
  https://docs.m5stack.com/#/ja/quick_start/m5stickc/m5stickc_quick_start
 上記ページを参考に、ボードマネージャとライブラリマネージャよりM5StickCをインストールしておきます。

インストールが必要なライブラリ
・ArduinoJson
  https://arduinojson.org/v6/doc/
 JSONを簡単に扱えるようになります。Arduino IDEのライブラリマネージャからインストールしておいて下さい。

本投稿の最後に、M5Stick-Cについているジャイロ、バッテリー容量、GroveのBME680の環境センサ、NTP(時刻)、Ambientへのアップロードもろもろを組み込んだサンプルソースも載せておきます。

WiFiアクセスポイントに接続する

ESP系であれば、標準のライブラリでできます。setup()で準備します。

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }
  Serial.println(WiFi.localIP());

POSTするJsonを作成する

JSONの生成には、ArduinoJsonを使いました。
まずは定義。

const int capacity = JSON_OBJECT_SIZE(2);
StaticJsonDocument<capacity> json_request;

メモリの省エネを意識しているようで、あらかじめセットする予定の項目の数を指定する必要があります。JSON_OBJECT_SIZEの部分です。セットする項目が配列だったり、階層化されていたりする場合の計算方法もあるので、ArduinoJsonのHPを参考にしてください。
今回は2個の項目を設定します。
あるいは、必要な容量は以下のページでも計算してくれます。

ArduinoJson Assistant
 https://arduinojson.org/v6/assistant/

Jsonの項目の設定はこんな感じ。

  json_request["counter"] = counter;
  json_request["tick"] = tick;

最後に、JSON文字列化(JSON.stringifyのことです)します。
出力先にバッファー(buffer)を用意しました。

char buffer[255];
serializeJson(json_request, buffer, sizeof(buffer));

JsonをPOSTする

POSTには、HTTPClientを使いました。

以下の感じです。

  HTTPClient http;
  http.begin(host);
  http.addHeader("Content-Type", "application/json");
  int status_code = http.POST((uint8_t*)buffer, strlen(buffer));
  if( status_code == 200 ){
    Stream* resp = http.getStreamPtr();

    DynamicJsonDocument json_response(255);
    deserializeJson(json_response, *resp);

    serializeJson(json_response, Serial);
    Serial.println("");
  }
  http.end();

http.POSTで、JSON文字列を載せてPOSTしています。

結果は、http.getStreamPtr()で取得します。
WiFiClient* が返ってくるのですが、Streamを継承しています。

POSTのレスポンスは、application/jsonであることを期待して、パースしています。
以下の部分で、JSON文字列をパースしています。

    DynamicJsonDocument json_response(255);
    deserializeJson(json_response, *resp);

(もちろん、DynamicJsonDocumentではなくStaticJsonDocumentを使っても構いません。)

値を取り出すときにはいったん変数に取り出すのが良いです。
そうすることで、変数の型に合わせて自動的に変換してくれます。
文字列を取り出すときには、constをつけるようにした方がメモリ効率上良いようです。

int status = json_response["status"];
const char* message = json_response["message"];

ソースコード

以上をまとめたソースコードは以下になります。

#include <M5StickC.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>

const char *ssid = "【WiFiアクセスポイントのSSID】";
const char *password = "【WiFiアクセスポイントのパスワード】";

const int capacity = JSON_OBJECT_SIZE(2);
StaticJsonDocument<capacity> json_request;
char buffer[255];

const char *host = "【POST接続するエンドポイント】";

unsigned long counter = 0;
unsigned long tick = 0;

void setup() {
  M5.begin();

  M5.Axp.ScreenBreath(9);
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);

  M5.Lcd.println("[M5StickC]");
  delay(1000);

  M5.Lcd.println("start Serial");
  Serial.begin(9600);
  while (!Serial);
  delay(100);

  M5.Lcd.println("start WiFi");
  Serial.print("start Wifi");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }
  Serial.println("");
  M5.Lcd.println("Connected");
}

void loop() {
  counter++;
  tick = millis();

  json_request["counter"] = counter;
  json_request["tick"] = tick;

  serializeJson(json_request, Serial);
  Serial.println("");

  serializeJson(json_request, buffer, sizeof(buffer));

  HTTPClient http;
  http.begin(host);
  http.addHeader("Content-Type", "application/json");
  int status_code = http.POST((uint8_t*)buffer, strlen(buffer));
  Serial.printf("status_code=%d\r\n", status_code);
  if( status_code == 200 ){
    Stream* resp = http.getStreamPtr();

    DynamicJsonDocument json_response(255);
    deserializeJson(json_response, *resp);

    serializeJson(json_response, Serial);
    Serial.println("");
  }
  http.end();

  delay(5000);
}

以下の部分は、各自の環境に合わせて変更してください。

【WiFiアクセスポイントのSSID】
【WiFiアクセスポイントのパスワード】
【POST接続するエンドポイント】

(参考)サンプルアプリ

ちょうど手元に、GroveのBME680があったので、つないでみました。

Grove - Temperature Humidity Pressure Gas Sensor(BME680)
 http://wiki.seeedstudio.com/Grove-Temperature_Humidity_Pressure_Gas_Sensor_BME680/

また、せっかくなので、Ambientを使わせていただいて、可視化してみました。

Ambient
 https://ambidata.io/

Arduino IDEに以下をインストールしておいて下さい。
 https://github.com/AmbientDataInc/Ambient_ESP8266_lib
 https://github.com/Seeed-Studio/Seeed_BME680

ソースコードは以下の通りです。

#include <M5StickC.h>
#include <WiFi.h>
#include <WiFiMulti.h>
#include <WiFiUdp.h>
#include <NTPClient.h>

#define BME6800

#ifdef BME6800

#include "seeed_bme680.h"

#include "Ambient.h"

WiFiClient client;
Ambient ambient;
unsigned int channelId = AmbientのチャネルID;
const char* writeKey = "【Ambientのライトキー】";

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

#define IIC_ADDR  uint8_t(0x76)

/**  NOTE!!!!!!!!!!!!  Select the communication protocol correctly **/

Seeed_BME680 bme680(IIC_ADDR); /* IIC PROTOCOL */
//Seeed_BME680 bme680;             /* SPI PROTOCOL */
//Seeed_BME680 bme680(BME_CS, BME_MOSI, BME_MISO,  BME_SCK);/*SPI PROTOCOL*/

#define MAX_COUNT_ENV   5

#endif

const char *ssid = "【WiFiアクセスポイントのSSID】";
const char *password = "【WiFiアクセスポイントのパスワード】";

bool ntp_updated = false;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, 9 * 60 * 60);

#define TIME_INTARVAL   1000
unsigned long next_time = TIME_INTARVAL;
unsigned long prev_time = 0;

#define MAX_COUNT_VBAT   10
unsigned long count = 0;
#define CHECK_COUNT(c,m)  (c != 0 && count < (m))

enum{
  MODE_GYRO = 0,
#ifdef BME6800
  MODE_ENV,
#endif
  MODE_VBAT,
  MODE_TIME,

  NUM_OF_MODE
};
unsigned char mode = MODE_GYRO;

void setup() {
  M5.begin();
  M5.IMU.Init();

  M5.Axp.ScreenBreath(9);
  M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setTextSize(2);

  M5.Lcd.println("[M5StickC]");
  delay(1000);

  M5.Lcd.println("start Serial");
  Serial.begin(9600);
  while (!Serial);
  delay(100);

  M5.Lcd.println("start WiFi");
  Serial.print("start Wifi");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(100);
  }
  Serial.println("");

  timeClient.begin();

#ifdef BME6800
  M5.Lcd.println("start BME680");
  while (!bme680.init()){
    Serial.println("bme680 init failed ! can't find device!");
    delay(10000);
  }

  ambient.begin(channelId, writeKey, &client);
#endif
}

void loop() {
  M5.update();

  if( !ntp_updated ){
    Serial.println("NTP updating");
    ntp_updated = timeClient.update();
    if( ntp_updated ){
      Serial.println("NTP updated");
    }
  }

  if( M5.BtnA.wasReleased() ){
    mode = (mode + 1) % NUM_OF_MODE;
    count = 0;
  }

  if( count > 0 ){
    unsigned long now = millis();
    if( ( ( next_time > prev_time ) && ( prev_time < now && now <= next_time ) ) ||
        ( ( next_time < prev_time ) && ( prev_time < now || now <= next_time ) ) )
      return;

    prev_time = next_time;
    next_time += TIME_INTARVAL;

    count++;
  }

  if( mode == MODE_GYRO ){
    int16_t accX, accY, accZ;
    int16_t gyroX, gyroY, gyroZ;
    M5.IMU.getGyroAdc(&gyroX, &gyroY, &gyroZ);
    M5.IMU.getAccelAdc(&accX, &accY, &accZ);

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.setTextSize(1);

    M5.Lcd.printf("gyrX: %.2f\n", ((float)gyroX) * M5.IMU.gRes);
    M5.Lcd.printf("gyrY: %.2f\n", ((float)gyroY) * M5.IMU.gRes);
    M5.Lcd.printf("gyrZ: %.2f\n", ((float)gyroZ) * M5.IMU.gRes);
    M5.Lcd.printf("accX: %.2f\n", ((float)accX) * M5.IMU.gRes);
    M5.Lcd.printf("accY: %.2f\n", ((float)accY) * M5.IMU.gRes);
    M5.Lcd.printf("accZ: %.2f\n", ((float)accZ) * M5.IMU.gRes);
  }else
#ifdef BME6800
  if( mode == MODE_ENV ){
    if(CHECK_COUNT(count, MAX_COUNT_ENV))
      return;

    if (bme680.read_sensor_data()){
      Serial.println("Failed to perform reading :(");
      return;
    }

    float tmp, hum, pres, gas;

    tmp = bme680.sensor_result_value.temperature;
    hum = bme680.sensor_result_value.humidity;
    pres = bme680.sensor_result_value.pressure / 100.0;
    gas = bme680.sensor_result_value.gas / 1000.0;

    Serial.printf("tmp: %2.1fC\r\n", tmp);
    Serial.printf("hum: %2.1f%%\r\n", hum);
    Serial.printf("prs: %3.0fhPa\r\n", pres);
    Serial.printf("gas: %2.0fKohm\r\n", gas);
    Serial.println("");

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.setTextSize(2);
    M5.Lcd.printf("tmp: %2.1fC\n", tmp);
    M5.Lcd.printf("hum: %2.1f%%\n", hum);
    M5.Lcd.printf("prs: %3.0fhPa\n", pres);
    M5.Lcd.printf("gas: %2.0fKohm\n", gas);

    ambient.set(1, String(tmp).c_str());
    ambient.set(2, String(hum).c_str());
    ambient.set(3, String(pres).c_str());
    ambient.set(4, String(gas).c_str());
    ambient.send();
  }else
#endif  
  if( mode == MODE_VBAT ){
    if(CHECK_COUNT(count, MAX_COUNT_VBAT))
      return;

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);

    double vbat = M5.Axp.GetVbatData() * 1.1 / 1000;
    M5.Lcd.setTextSize(3);
    M5.Lcd.printf("v_bat:\r\n%4.2f", vbat);
  }else if( mode == MODE_TIME ){
    String time_str = timeClient.getFormattedTime();

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setCursor(0, 0);
    M5.Lcd.setTextSize(3);

    M5.Lcd.println(time_str);
  }

  count = 1;
}

以下の部分は、各自の環境に合わせて変更してください。

【WiFiアクセスポイントのSSID】
【WiFiアクセスポイントのパスワード】
【POST接続するエンドポイント】
【AmbientのチャネルID】
【Ambientのライトキー】

5秒ごとに、AmbientにBME680から取得した環境情報を上げています。
Ambientのページを見てみると、こんな感じで可視化されました。

image.png

以上

63
60
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
60