前回まで
今回の内容
今回は、AWS IoT向けにデバイスからpublishするところまで作業します。
先にデバイス側のコードを紹介
aws-iot-publish.ino
#include <time.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include "BME280SPI.h"
#define JST 3600*9
const String wifiSSID = "<yourSSID>"; // お使いのWiFiのSSIDを入力してください
const String wifiPass = "<yourPass>"; // お使いのWiFiのパスワードを入力してください
const uint8_t pinLED = 14; // 緑LEDピン番号
const uint8_t pinSW = 0; // タクトスイッチSW1ピン番号
const uint8_t pinBME280CS = 15; // BME280センサーCSピン番号
const uint8_t pinSDCS = 2; // SDカードCSピン番号
const char* server = "<AWS IoTのエンドポイント>";
const int port = 8883;
const char* pubTopic = "<トピック名>";
const char* Root_CA = \
"-----BEGIN CERTIFICATE----\n" \
"~\n" \
"~\n" \
"中略\n" \
"-----END CERTIFICATE-----\n";
const char* Client_cert = \
"-----BEGIN CERTIFICATE-----\n" \
"~\n" \
"~\n" \
"中略\n" \
"-----END CERTIFICATE-----\n";
const char* Client_private = \
"-----BEGIN RSA PRIVATE KEY-----\n" \
"~\n" \
"~\n" \
"中略\n" \
"-----END RSA PRIVATE KEY-----\n";
WiFiClientSecure espClient;
PubSubClient mqttClient(espClient);
//---------------------------------
// 変数
BME280SPI bme280(pinBME280CS); // BME280センサーを制御するクラス
bool statWiFiConnected = true; // WiFiに接続されているかを記憶しておく
uint32_t lastStat = 0; // 最後にstat()を実行した時間を記憶しておく
char currentTimeStr[19];
//---------------------------------
// 関数
void connectAWSIoT() {
while (!mqttClient.connected()) {
if (mqttClient.connect("ESP8266")) {
Serial.println("AWSIoT Connected.");
} else {
Serial.print("Failed. Error state=");
Serial.print(mqttClient.state());
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void mqttCallback (char* topic, byte* payload, unsigned int length) {
Serial.print("Received. topic=");
Serial.println(topic);
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.print("\n");
}
void mqttLoop() {
if (!mqttClient.connected()) {
connectAWSIoT();
}
mqttClient.loop();
}
void getCurrentDateTimeStr() {
time_t t;
struct tm *tm;
t = time(NULL);
tm = localtime(&t);
sprintf(currentTimeStr,"%04d/%02d/%02d %02d:%02d:%02d",
tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
}
void setup() {
Serial.begin(115200);
delay(1);
Serial.printf("\n\n-----------------------\n");
Serial.printf("Program Start\n");
pinMode(pinLED, OUTPUT); // LED
pinMode(pinSW, INPUT); // SW1
// SPI CSピンをHighにする
pinMode(pinBME280CS, OUTPUT);
pinMode(pinSDCS, OUTPUT);
digitalWrite(pinBME280CS, HIGH);
digitalWrite(pinSDCS, HIGH);
// BME280 Measure until success
while(BME280SPI::statSuccess!=bme280.meas()){
Serial.print("BME280 is not available\n");
delay(5000);
}
// WiFi
Serial.printf("\nWiFi");
WiFi.mode(WIFI_STA);
WiFi.begin(wifiSSID.c_str(), wifiPass.c_str());
// 接続完了まで待つ
while(WiFi.status()!=WL_CONNECTED){
delay(1000);
}
Serial.printf(" Connected IP : ");
Serial.println(WiFi.localIP());
//時刻合わせ
configTime( JST, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
delay(5000);
getCurrentDateTimeStr();
Serial.printf("Current date-time is ");
Serial.println(currentTimeStr);
//AWSへの接続処理
//証明書、秘密鍵のオブジェクト作成
BearSSL::X509List cert(Root_CA);
BearSSL::X509List client_crt(Client_cert);
BearSSL::PrivateKey key(Client_private);
//証明書、秘密鍵の設定
espClient.setTrustAnchors(&cert);
espClient.setClientRSACert(&client_crt, &key);
//接続処理
mqttClient.setServer(server, port);
mqttClient.setCallback(mqttCallback);
connectAWSIoT();
}
// 5秒おきに動作状況を確認する処理
void stat(){
pinMode(pinLED, OUTPUT);
if(WiFi.status()==WL_CONNECTED){
// WiFi切断 -> 接続に変わった場合ログを残す
if(!statWiFiConnected){
statWiFiConnected = true;
Serial.println(" WiFi Connected");
Serial.println(WiFi.localIP());
}
// 正常状態 LEDを1回点滅
digitalWrite(pinLED, 1);
delay(100);
digitalWrite(pinLED, 0);
}else{
// WiFi接続 -> 切断に変わった場合ログを残す
if(statWiFiConnected){
statWiFiConnected = false;
Serial.printf(" WiFi Disconnected\n");
}
// WiFi切断状態 LEDを2回点滅
digitalWrite(pinLED, 1);
delay(50);
digitalWrite(pinLED, 0);
delay(450);
digitalWrite(pinLED, 1);
delay(50);
digitalWrite(pinLED, 0);
}
}
// センサーの気温、気圧、湿度をAWSにpublishする
void publishToAWS() {
Serial.printf("Start publishing data to AWS\n");
// 測定処理おこなう
bme280.meas();
// センサーからデータ取得に失敗
if(!bme280.getResult()){
Serial.printf(" BME280 Not Available\n");
return;
}
getCurrentDateTimeStr();
String json = "{";
json += "\"time\":\"";
json += currentTimeStr,
json += "\",";
json += "\"temperature\":\"";
json += bme280.getMeasStr(BME280SPI::selectTemp).c_str();
json += "\",";
json += "\"humidity\":\"";
json += bme280.getMeasStr(BME280SPI::selectHumidity).c_str();
json += "\",";
json += "\"temperature\":\"";
json += bme280.getMeasStr(BME280SPI::selectPressure).c_str();
json += "\"}";
Serial.println(json);
if(!mqttClient.publish(pubTopic, json.c_str())){
Serial.println("Failed to publish\n");
}else{
Serial.println("Succeed to publish\n");
}
}
void loop() {
if(millis() > lastStat+5000){ // 5秒おきに実行
lastStat = millis();
stat();
publishToAWS();
}
delay(1); // 消費電力低減
mqttClient.loop();
}
ポイント
全体的に
- ループとかwifi接続とかの実装はこちらの公式サンプルを参考に実装。
- とりあえずpublishすることを優先したので、実装的に変なとこありますがご容赦ください。
証明書・秘密鍵について
- とりあえず今回はベタ書きしました。
- 最初、ここのやり方を参考に実装したけどうまくいかず。
- こんなエラーが出た
error: no matching function for call to BearSSL::WiFiClientSecure::setCACert(const char*&)
- こんなエラーが出た
- 参考にしてた人のライブラリがESP32用で、
WifiSecureClient
の実装が同名ながら違っていた(今回使ったのはESP8266
) - 最終的にここを見て解決
- この参考記事には時刻合わせも必要という記載があったのでここを参考に実装しました。
センサー情報の取得について
- 公式のサンプルを元に実装しました。
掲載コード以外でやったこと
- 最初のほう、
fork/exec /Users/yourusername/Library/Arduino15/packages/esp8266/tools/python3/3.7.2-post1/python3: no such file or directory
というエラーが出ました。- この記事を参考に対処して解決。
-
PubSubClient.h
のパラメーターをいじりました。-
MQTT_MAX_PACKET_SIZE
を256
に。
- publishに失敗する原因を切り分けてるうちに、publishするデータ長によって成否が分かれたので気づいた。 -
MQTT_KEEPALIVE
を30
に。- ここに書いてたのでとりあえず。
-
- とりあえず動作確認する際は、AWS IoTのテスト機能でpublishしたデータをsubscribeできるか確認しました。デバイスがpublishするトピックをテスト機能のほうでsubscribeして、うまくいけばpublishされたデータが見れるようになります。
次回やる予定
publishしたデータをDynamoDBにぶち込んで何しか可視化したいと思います。
と思ったけどS3にしました。
次回記事
家の環境(温度・湿度・大気圧)をAWSに蓄積して分析してみるの巻 その3
他、参考記事
https://blog.maripo.org/2017/07/esp32-aws-iot/
https://blog.maripo.org/2017/07/esp32-aws-iot-troubleshooting/