イントロダクション
既に多くの方が記事を書いているように、ESP8266はArduino IDEを使って開発することもできます。そこで、IDCF Cloudのチュートリアルである「myThingsをはじめよう」を元に、Raspberry PiではなくESP8266を使ってIDCF Cloudまで接続できるようにしてみました。
参考にした記事はMasato Shimizuさんの「ArduinoからDHT11の温度と湿度データをMQTTを使ってMeshbluに送信する」です。この記事が書かれたのは今年の4月20日なのですが、その後にArduino Client for MQTTは更新され、若干APIが変わりました。また、この記事はArduino Ethernet Shieldを対象に書かれていますが、Arduino Client for MQTTはESP8266でも動作するため、かなりコンパクトかつ安価に実現できます。
プログラミング:ESP8266からBME280環境センサのデータ送信
これは、Arduino Client for MQTTに含まれるサンプルmqtt_publish_in_callbackとMasato Shimizuさんの記事中のサンプルを元にしたものです。BME280を使用するライブラリやサンプルには様々なものがありますが、ここではのEmbedded AdventuresのIan Harris氏によるライブラリを使用しています。
ファイルは2つに分かれています。Wi-FiアクセスポイントやIDCF CloudでのUUIDやトークンなど、各自で変更する必要のあるものはconfig.hという名前で別のタブ(実体はファイル)にして分離しています。
// MQTTのクライアントID(ユニークにするためにスケッチをコンパイルした日付と時刻を使用)
const char *mqtt_client_id = "kotobuki" __DATE__ __TIME__;
// Wi-FiアクセスポイントのSSIDとパスワード
const char *ssid = "********";
const char *pass = "********";
// IDCF Cloudに関する設定
const char *server = "210-***-***-***.jp-east.compute.idcfcloud.com";
const char* trigger_1_uuid = "********-****-****-****-************";
const char* trigger_1_token = "********";
const char* action_1_uuid = "********-****-****-****-************";
const char* action_2_uuid = "********-****-****-****-************";
#include <Wire.h>
#include <ESP8266WiFi.h>
// MQTTを扱うためのライブラリ
// https://github.com/Imroy/pubsubclient
#include <PubSubClient.h>
// BME280を扱うためのライブラリ
// https://github.com/embeddedadventures/BME280
#include <BME280_MOD-1022.h>
#include "config.h"
WiFiClient wclient;
// MQTTクライアント
PubSubClient client(wclient, server);
// 温度と湿度、気圧を格納する変数
float temperature = 0.0;
float humidity = 0.0;
float pressure = 0.0;
// コールバック関数
void callback(const MQTT::Publish& pub) {
// コールバックで必要な処理があればここで
}
// JSONフォーマットでペイロードを用意
String getPayloadInJson() {
String json = "{";
json += "\"devices\":";
json += "[\"";
json += action_1_uuid;
json += "\"";
json += ",";
json += "\"";
json += action_2_uuid;
json += "\"";
json += "],";
json += "\"payload\":";
json += "{";
json += "\"pressure\":\"";
json += pressure;
json += "\",";
json += "\"humidity\":\"";
json += humidity;
json += "\",";
json += "\"temperature\":\"";
json += temperature;
json += "\"}";
json += "}";
return json;
}
// 浮動小数点数を小数点以下2桁でシリアルにプリント
void printFormattedFloat(float val) {
char buffer[10];
dtostrf(val, 4, 2, buffer);
Serial.print(buffer);
}
void setup() {
// IO4: SDA
// IO14: SCL
Wire.begin(4, 14);
Serial.begin(115200);
delay(10);
Serial.println();
Serial.println();
// BME280の補償値を読み取る
BME280.readCompensationParams();
// オーバーサンプリングの回数を設定
BME280.writeOversamplingTemperature(os1x);
BME280.writeOversamplingHumidity(os1x);
BME280.writeOversamplingPressure(os1x);
}
void loop() {
// Wi-Fiアクセスポイントに接続していなければ接続
if (WiFi.status() != WL_CONNECTED) {
Serial.print("Connecting to ");
Serial.print(ssid);
Serial.println("...");
WiFi.begin(ssid, pass);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
Serial.println("Failed");
return;
} else {
Serial.println("WiFi connected");
}
}
// クライアントがICDF Cloudのサーバに接続されていなければ接続
if (!client.connected()) {
// トリガー1のUUIDとトークンで認証
MQTT::Connect mqttConnect(mqtt_client_id);
mqttConnect.set_auth(trigger_1_uuid, trigger_1_token);
if (client.connect(mqttConnect)) {
client.set_callback(callback);
Serial.println("Client connected");
} else {
Serial.println("Client failed to connect");
}
}
// クライアントがICDF Cloudのサーバに接続されていたら以下の処理を実行
if (client.connected()) {
// BME280を1度だけ測定を行うモードに設定し計測が終わるまで待機
BME280.writeMode(smForced);
while (BME280.isMeasuring()) {
delay(1);
}
// BME280から測定値を読み取る
BME280.readMeasurements();
// 温度と湿度、気圧を取得
temperature = BME280.getTemperature();
humidity = BME280.getHumidity();
pressure = BME280.getPressure();
// 温度と湿度、気圧をシリアルにプリント
Serial.print("Temperature: ");
printFormattedFloat(temperature);
Serial.println();
Serial.print("Humidity : ");
printFormattedFloat(humidity);
Serial.println();
Serial.print("Pressure : ");
printFormattedFloat(pressure);
Serial.println();
// IDCF CloudにMQTTでデータを送信
bool sent = client.publish("message", getPayloadInJson());
if (sent) {
Serial.println("Sent");
} else {
Serial.println("Failed to send");
}
Serial.println();
}
delay(10000);
}
myThingsアプリと接続:myThingsから「IDCF」チャンネルを使う
これは、Arduino Client for MQTTに含まれるサンプルmqtt_subscriberを元にしたものです。オリジナルの記事では、天気・災害のトリガーをfreeboard上で確認するようになっています。このサンプルは受け取ったペイロードをそのままシリアルにプリントしているだけですが、内容に応じてLEDの色を変えて表示する、ディスプレイに文字で表示する、サーボの動きで表現するなど、様々な展開ができると思います。
前のサンプルと同様にファイルは2つに分かれています。Wi-FiアクセスポイントやIDCF CloudでのUUIDやトークンなど、各自で変更する必要のあるものはconfig.hという名前で別のタブ(実体はファイル)にして分離しています。
// MQTTのクライアントID(ユニークにするためにスケッチをコンパイルした日付と時刻を使用)
const char *mqtt_client_id = "kotobuki" __DATE__ __TIME__;
// Wi-FiアクセスポイントのSSIDとパスワード
const char *ssid = "********";
const char *pass = "********";
// IDCF Cloudに関する設定
const char* action_1_uuid = "********-****-****-****-************";
const char* action_1_token = "********";
IPAddress server(210, ***, ***, ***);
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include "config.h"
#define BUFFER_SIZE 100
// メッセージを受け取ったらシリアルにプリント
void callback(const MQTT::Publish& pub) {
Serial.print(pub.topic());
Serial.print(" => ");
if (pub.has_stream()) {
// ペイロードのサイズが大きい場合にはローカルに用意したバッファに分割して読み取り
// 読み取った単位ごとにシリアルにプリント
uint8_t buf[BUFFER_SIZE];
int read;
while (read = pub.payload_stream()->read(buf, BUFFER_SIZE)) {
Serial.write(buf, read);
}
pub.payload_stream()->stop();
Serial.println();
} else {
// ペイロードのサイズが小さい場合にはそのままシリアルにプリント
Serial.println(pub.payload_string());
}
}
WiFiClient wclient;
PubSubClient client(wclient, server);
void setup() {
Serial.begin(115200);
delay(10);
Serial.println();
Serial.println();
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
Serial.print("Connecting to ");
Serial.print(ssid);
Serial.println("...");
WiFi.begin(ssid, pass);
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
return;
}
Serial.println("WiFi connected");
}
if (!client.connected()) {
// アクション1のUUIDとトークンをユーザ名およびパスワードとしてサーバに接続
MQTT::Connect mqttConnect(mqtt_client_id);
mqttConnect.set_auth(action_1_uuid, action_1_token);
if (client.connect(mqttConnect)) {
client.set_callback(callback);
client.subscribe(action_1_uuid);
}
}
if (client.connected()) {
client.loop();
}
}