知的好奇心 for IoT

IoT関連の知的好奇心を探求するブログです

Machinistに時系列データを送るサンプルとして、ESP8266の間欠動作でBME280のデータを送れるようにした

突然ですが、『Machinist、みなさん使ってますか?

ホームページでは「メトリクス」という耳慣れないものの管理をするようなことが書かれていますが、IIJのプレスリリースでは「データ可視化・監視サービス」って誰にでもわかる言葉で書いてあるんですよ。IT業界ってかっこつけたがるんですよね。

まずは、どんな風に見えるか、ぼくが送ったデータのダッシュボードのキャプチャを載せますね。

f:id:IntellectualCuriosity:20191002113958p:plain

こんな感じの表示なら、殆ど手間をかけずに作ることができます。

ただ、所々にこのサービスの「かっこつけ」が垣間見れ、「ダークUI」に切り替えるとこんな風になります。

f:id:IntellectualCuriosity:20191002114656p:plain

ぼくも普段は暗い方が目に優しいのでダークモードを良く使うようになってるんですが、このカラーリングは...逆に目が痛い!情報が入ってこない!ので、使ってません。

 

特徴的なデータ収集方法

Machinistを使ってみて1番変わってるなと思ったのが、データの収集方法です。

普通のサービスだと、温度と湿度の2つの項目を収集するなら、事前に温度と湿度の項目を定義しておく必要がありますよね。

ThingSpeakだとChannel Settingsで使用するフィールドにチェックを付けるようになっています。

f:id:IntellectualCuriosity:20191002121659p:plain

しかし、Machinistはデータを定義する設定画面がないんです。

Machinistでは事前に定義をしていなくても、送られてくるJSONデータ形式をそのまま取り込むようになっています。一見非常に便利そうに感じますが、JSONのデータ構造が複雑になるのと、JSONでしかデータを送ることができなくなるため、Arduinoのような軽いエッジデバイスにとってはかえってデメリットになります。

上で紹介したグラフの元となるESP8266が送っているJSONデータはこんな感じで、非常に面倒くさいです。

{
  "agent_id": "XXXXXXXXXXXXXXXX",
  "metrics": [
    {
      "name": "Temperature",
      "namespace": "BME280",
      "data_point": {
        "value": 28.2
      }
    },
    {
      "name": "Humidity",
      "namespace": "BME280",
      "data_point": {
        "value": 52.1
      }
    },
    {
      "name": "Pressure",
      "namespace": "BME280",
      "data_point": {
        "value": 1019
      }
    }
  ]
}

ThingSpeakの場合は温度・湿度・気圧を送るのにこんなシンプルな形式で済みます。

{
  "api_key":"XXXXXXXXXXXXXXXX",
  "field1":28.2,
  "field2":52.1,
  "field3":1019
}

ずいぶん違いますよね。

 

厄介なHTTPS送信

ESP8266を使ってMachinistにデータを送るのにはもう1つ厄介なことがあります。データの送信先https://gw.machinist.iij.jp/endpointHTTPSオンリーになっていることです。

Arduino IDEでESP8266を使えるようにするためには、ボードマネージャでESP8266のボード情報をインストールする必要があります。

f:id:IntellectualCuriosity:20191002135307p:plain

このバージョンが2.4.xまではaxTLSというモジュールが使われていたのですが、2.5.xになってaxTLSは非推奨扱いになり以前のコードがコンパイルできなくなりました。ドキュメントにはTLS 1.2がサポートされていないからと書いてあります。

2.5.xではaxTLSに代わってBearSSLというモジュールが採用されているんですが、中間者攻撃を完全に防ぐにはアクセス先のサーバー証明書を発行したルート証明書を取り込む必要がありとんでもなく面倒なんです。

正直、そんなところまで自分のプログラムで面倒を見なくて済むラズパイなんかを使うのが正解なんでしょうが、単に温度や湿度のデータを送るだけだとラズパイはオーバースペックだし、電源をブツ切りできないし、バッテリー運用もしずらい!

結局、妥協としてBearSSLのサンプルプログラムに載っていた証明書を使わないで最も処理が軽い方法を使うことにしました。BearSSLを使うときは160MHzにしろとか書かれてあったんですが、これだと80MHzのままでも問題なく動くんです。

該当部分のコードはこんな感じになります。

BearSSL::WiFiClientSecure client;
client.setInsecure();
client.setCiphersLessSecure();
client.connect(gw.machinist.iij.jp, 443);

 

ハードウェア

今回使ったパーツはこれです。

写真はこうなんですが...、基板の裏にも配線してるのでわかんないですよね。

f:id:IntellectualCuriosity:20191002151209p:plain

配線

配線はいたって簡単でESP8266とBME280はI2C接続、センサーの電源はGPIO13、Deep Sleepから起きるためにGPIO16とRSTを繋いでいます。

f:id:IntellectualCuriosity:20191002163452p:plain

 

ソフトウェア

ボードマネージャ

ESP8266のボード情報はバージョン2.5.xを使います。

f:id:IntellectualCuriosity:20191002135307p:plain

ライブラリマネージャ

ArduinoJsonのバージョンは6.xだとコンパイルでエラーになるので、バージョン5.xを使ってください。

f:id:IntellectualCuriosity:20191002165756p:plain

BME280にはAdafruit Unified SensorとAdafruit BME280 Libraryを使います。

f:id:IntellectualCuriosity:20191002170925p:plain

f:id:IntellectualCuriosity:20191002171515p:plain

スケッチ

スケッチを動作させるには、事前にMachinistのアカウントとエージェントを作っておく必要があります。

スケッチはESP8266のDeep Sleep機能を利用してDS_INTERVALで指定した分数毎に動くようになっているので、loop()にはコードを書いていません。

MachinistのJSONデータ形式はシンプルではないため、わかりやすいようにテンプレートの値だけを変更するようにしています。

/*
 * BME280 sensor program for Machinist using ESP8266
 * October 02, 2019
 * By Hiroyuki ITO
 * https://intellectualcuriosity.hatenablog.com/  
 * MIT Licensed.
 */
#include <Wire.h>
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <Adafruit_BME280.h>

#define DS_INTERVAL        1 // Deep Sleep Interval Time (minutes)
#define WIFI_SSID          "YourNetworkName"
#define WIFI_PASS          "YourPassword"
#define MACHINIST_AGENT_ID "YourMachinistAgentID"
#define MACHINIST_API_KEY  "YourMachinistAPIKey"
#define MACHINIST_HOST     "gw.machinist.iij.jp"
#define MACHINIST_PORT     443
#define MACHINIST_PATH     "/endpoint"
#define VCC_PIN            13 // Sensor VCC Pin

// Global Objects
Adafruit_BME280 bme;       // I2C
String temperature;
String humidity;
String pressure;

// JSON Data Template
char json[] =
"{"
  "\"agent_id\":\"" MACHINIST_AGENT_ID "\","
  "\"metrics\": ["
  "{"
    "\"name\": \"Temperature\","
    "\"namespace\": \"BME280\","
    "\"data_point\": {"
      "\"value\": 25.0"  // dummy
    "}"
  "},"
  "{"
    "\"name\": \"Humidity\","
    "\"namespace\": \"BME280\","
    "\"data_point\": {"
      "\"value\": 50.0"  // dummy
    "}"
  "},"
  "{"
    "\"name\": \"Pressure\","
    "\"namespace\": \"BME280\","
    "\"data_point\": {"
      "\"value\": 1000"  // dummy
    "}"
  "}]"
"}";

/*
 * Get Sensor Data
 */
void GetSensorData() {
  // Sensor Power ON
  pinMode(VCC_PIN, OUTPUT);      
  digitalWrite(VCC_PIN, HIGH);

  // Initialize BME280
  delay(10);
  bme.begin(0x76); // SDO GND(Open):0x76 VDD:0x77

  // Sensor Measurement
  temperature = String(bme.readTemperature(), 1); // xx.xC
  humidity = String(bme.readHumidity(), 1);       // xx.x%
  pressure = String(bme.readPressure()/100.0, 0); // xxxxhPa
  Serial.println("Temperature:" + temperature);
  Serial.println("Humidity   :" + humidity);
  Serial.println("Pressure   :" + pressure);

  // Sensor Power OFF
  digitalWrite(VCC_PIN, LOW);
}

/*
 * Send Sensor Data
 */
void SendSensorData() {
  // Make JSON data
  StaticJsonBuffer<1000> jsonBuffer;
  JsonObject& root = jsonBuffer.parseObject(json);
  String JSBuffer;
  root["metrics"][0]["data_point"]["value"] = temperature.toFloat();
  root["metrics"][1]["data_point"]["value"] = humidity.toFloat();
  root["metrics"][2]["data_point"]["value"] = pressure.toInt();
  root.prettyPrintTo(JSBuffer);
  Serial.println(JSBuffer);
  JSBuffer = "";
  root.printTo(JSBuffer);

  // The ciphers used to set up the SSL connection can be configured to
  // only support faster but less secure ciphers.
  BearSSL::WiFiClientSecure client;
  client.setInsecure();
  client.setCiphersLessSecure();
  Serial.print("Trying: " MACHINIST_HOST ":" + String(MACHINIST_PORT) + "... ");
  client.connect(MACHINIST_HOST, MACHINIST_PORT);
  if (!client.connected()) {
    Serial.println("Can't connect to Machinist");
    return;
  }
  Serial.println("Connected");
  client.println("POST " MACHINIST_PATH " HTTP/1.0");
  client.println("Host: " MACHINIST_HOST);
  client.println("Authorization: Bearer " MACHINIST_API_KEY);
  client.println("Content-Type: application/json");
  client.println(String("Content-Length: ") + JSBuffer.length());
  client.println();
  client.println(JSBuffer);
  while (!client.available()) {
    delay(10);
  }
  Serial.print("Sent Data to Machinist\nThe response code is ");
  Serial.println(client.readStringUntil('\r'));
  client.stop();
}

void setup() {
  Serial.begin(74880);

  // Connect to WiFi
  Serial.print("Connecting to " WIFI_SSID);
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);     
    Serial.print(".");
  } 
  Serial.println("\nWiFi connected");

  GetSensorData();
  SendSensorData();

  // Go to deep sleep
  delay(1000);
  Serial.println(String("Go to sleep for ") + DS_INTERVAL + " minutes");
  ESP.deepSleep(DS_INTERVAL*60*1000*1000, WAKE_RF_DEFAULT);
}

void loop() {
}

 

Machinistは制限がありますが無料枠でも十分に楽しめるので、ESP8266でいろいろと遊んでください!

 

おしまい