家の環境（温度・湿度・大気圧）をAWSに蓄積して分析してみるの巻　その２

# 前回まで
- [家の環境（温度・湿度・大気圧）をAWSに蓄積して分析してみるの巻　その１](https://qiita.com/KG_/items/c4a50a072f992dde78a3)

# 今回の内容
今回は、AWS IoT向けにデバイスからpublishするところまで作業します。

# 先にデバイス側のコードを紹介
```C: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接続とかの実装は[こちらの公式サンプル](https://www.indoorcorgielec.com/resources/%e5%bf%9c%e7%94%a8%e4%be%8b/esp-irtph-monitor%e3%82%92%e3%83%96%e3%83%a9%e3%82%a6%e3%82%b6%e3%81%8b%e3%82%89%e5%88%b6%e5%be%a1/)を参考に実装。
- とりあえずpublishすることを優先したので、実装的に変なとこありますがご容赦ください。

## 証明書・秘密鍵について
- とりあえず今回はベタ書きしました。
- 最初、[ここ](https://qiita.com/rockguitar6/items/4f028500d520dbf14be1)のやり方を参考に実装したけどうまくいかず。
    - こんなエラーが出た  
`error: no matching function for call to BearSSL::WiFiClientSecure::setCACert(const char*&)`
- 参考にしてた人のライブラリがESP32用で、`WifiSecureClient`の実装が同名ながら違っていた（今回使ったのは`ESP8266`）
- 最終的に[ここ](https://flogics.com/wp/ja/2019/07/esp8266-arduino-aws-iot-mqtt/)を見て解決
    - この参考記事には時刻合わせも必要という記載があったので[ここ](https://qiita.com/h_nari/items/d0374d1e1e36b9d988c0)を参考に実装しました。

## センサー情報の取得について
- [公式のサンプル](https://www.indoorcorgielec.com/wp-content/uploads/products/esp-irtph-monitor/bme280spi-sample.zip)を元に実装しました。

## 掲載コード以外でやったこと
- 最初のほう、`fork/exec /Users/yourusername/Library/Arduino15/packages/esp8266/tools/python3/3.7.2-post1/python3: no such file or directory`というエラーが出ました。
    - [この記事](https://dhiravat.com/arduino-1-8-10-cannot-compile-on-macos-catalina/)を参考に対処して解決。
- `PubSubClient.h`のパラメーターをいじりました。
    - `MQTT_MAX_PACKET_SIZE`を`256`に。
          - publishに失敗する原因を切り分けてるうちに、publishするデータ長によって成否が分かれたので気づいた。
    - `MQTT_KEEPALIVE`を`30`に。
        - [ここ](https://qiita.com/makotaka/items/388fa1ee0eb1f0237012#_reference-40c401b6967e0884efb2)に書いてたのでとりあえず。
- とりあえず動作確認する際は、AWS IoTのテスト機能でpublishしたデータをsubscribeできるか確認しました。デバイスがpublishするトピックをテスト機能のほうでsubscribeして、うまくいけばpublishされたデータが見れるようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411926/f10554c2-9095-262a-48c8-492e74d4549d.png)


# 次回やる予定
publishしたデータをDynamoDBにぶち込んで何しか可視化したいと思います。
と思ったけどS3にしました。

# 次回記事
[家の環境（温度・湿度・大気圧）をAWSに蓄積して分析してみるの巻　その３](https://qiita.com/KG_/items/18e435a4bc8f506f505f)

# 他、参考記事
https://blog.maripo.org/2017/07/esp32-aws-iot/
https://blog.maripo.org/2017/07/esp32-aws-iot-troubleshooting/