ESP32をAWSに接続してみた(1) ハードウェアの製作

2022.04.08

こんにちは、CX事業本部 IoT事業部のアッキーです。私は入社したてでAWSクラウドに不慣れなため、ESP32をIoT Coreに接続し、センサで取得したデータをアップロード、そしてそのデータを可視化するシステムの構築を通してAWSの練習をしましたので、手順をご紹介します。

まずはハードウェアを製作しました。

ハードウェア

メインはAWSで構築するシステムですのでハードウェアはなんでもいいのですが、ひとまず安く手軽に使えるWi-FiマイコンとしてESP32を選びました。

センサとしては最近出た温湿度センサのAHT25を使ってみます。また、これだけではありがちで面白くないので、ほこりセンサGP2Y1010AU0Fも使ってみました。ほこりセンサという名前ですが、花粉なども検知できるそうです。 そして、単体では動作しているかわかりませんので、デバッグ用にキャラクタ液晶も用意しました。

主要部品は以下の通りです。すべて秋月電子通商で購入しました。

  • ESP32ボード(秋月 AE-ESP32-WROOM-32E-MINI)
  • ほこりセンサ(SHARP GP2Y1010AU0F)
  • 温湿度センサ(ASAIR AHT25)
  • I2Cキャラクタ液晶(秋月 AE-AQM0802+PCA9515)

その他に、以下のパーツが必要です。

  • USB-UARTコンバータ(秋月 AE-UM232Rなどの信号線を3.3Vに設定可能なもの)
  • 抵抗150Ω ×1
  • 抵抗2.2kΩ ×5
  • 電解コンデンサ220μF ×1
  • Nch-MOSFET 2N7000 ×1

Nch-MOSFETは3.3Vでスイッチングに使用できる品番であれば代替できると思います。2SC1815などのNPNトランジスタも使用できるでしょう。

回路図

電源兼ESP32の書き込み用にUSB-UARTモジュールを接続しています。信号線が3.3Vのものを使用して下さい。

ほとんどそれぞれのデータシート通りに接続すれば問題ありませんが、唯一変更した点はGP2Y1010AU0FのVoを2つの2.2kΩで分圧した点です。データシートによると、Voからはほこりの濃度に応じた電圧が出力されますが、最高濃度ではこの電圧が3.7V程度になりますので、ESP32のADCの入力電圧を超えてしまいます。今回は簡単に抵抗で分圧して1/2の電圧を得ることにしました。

Vo電圧は4.7kΩ時で規定されているので、おそらく4.4kΩでも大丈夫だと仮定していますが、気になる方はもう少し大きな抵抗を使用するといいかもしれません。

今回はブレッドボード上に組みました。

ソフトウェア

Arduinoで開発しました。WiFIに接続し、60秒ごとにデータを測定し、MQTTでIoT Coreにデータを送信します。 ビルドには、ESP32のボード定義のほか、以下のライブラリが必要です。

ソースコード

esp32airnode.ino

#include <Wire.h>
#include <ST7032_asukiaaa.h>
#include <CRC8.h>
#include <WiFiClientSecure.h>
#include <MQTTClient.h>
#include <ArduinoJson.h>
#include "Secrets.h"

const int I2C_SDA = 14;
const int I2C_SCL = 13;
const int PARTICLE_LED_PIN = 32;
const int PARTICLE_INPUT_PIN = 34;
const byte AHT25_ADDR = 0x38;

const char* AWS_IOT_PUBLISH_TOPIC = "node/airsensor";

const int WIFI_TIMEOUT_MS = 20000;
const int MQTT_TIMEOUT_MS = 5000;

WiFiClientSecure net = WiFiClientSecure();
MQTTClient client = MQTTClient(256);
CRC8 crc;
ST7032_asukiaaa lcd;

double temperature;
double humidity;
uint32_t particle;


void delay_with_client_loop(unsigned long ms) {
  unsigned long start = millis();
  while((millis() - start) < ms) {
    client.loop();
  }
}

void connectWiFi() {
  if(WiFi.status() == WL_CONNECTED) {
    return;
  }
  
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.println("Connecting to Wi-Fi");
  lcd.clear();
  lcd.print("ConnWiFi");

  int wifi_connecting = 0;
  while ((WiFi.status() != WL_CONNECTED) && (wifi_connecting <= WIFI_TIMEOUT_MS)){
    delay(500);
    wifi_connecting += 500;
    Serial.print(".");
  }
  if(WiFi.status() != WL_CONNECTED) {    
    Serial.println("Wi-Fi Timeout");
    WiFi.disconnect();
    
    lcd.clear();
    lcd.print("WiFi NG");

    delay(30000);
    ESP.restart();
    return;
  }

  // Configure WiFiClientSecure to use the AWS IoT device credentials
  net.setCACert(AWS_CERT_CA);
  net.setCertificate(AWS_CERT_CRT);
  net.setPrivateKey(AWS_CERT_PRIVATE);
  return;
}

void connectAWS() {
  if(client.connected()){
    return;
  }
  
  // Connect to the MQTT broker on the AWS endpoint we defined earlier
  client.begin(AWS_IOT_ENDPOINT, 8883, net);
  client.setTimeout(1000);

  Serial.println("Connecting to AWS IOT");
  lcd.clear();
  lcd.print("Conn AWS");

  int client_connecting = 0;
  while ((!client.connect(THINGNAME)) && (client_connecting <= MQTT_TIMEOUT_MS)) {
    Serial.print(".");
    delay(100);
    client_connecting += 100;
  }
  if(!client.connected()){
    WiFi.disconnect(true, true);
    
    Serial.println("AWS IoT Timeout!");
    lcd.clear();
    lcd.print("AWS NG");

    delay(30000);
    ESP.restart();
    return;
  }

  Serial.println("AWS IoT Connected!");
  
  lcd.clear();
  lcd.print("AWS OK");
  return;
}

void publishMessage()
{
  StaticJsonDocument<200> doc;
  doc["temperature"] = temperature;
  doc["humidity"] = humidity;
  doc["particle"] = particle;
  
  char jsonBuffer[512];
  serializeJson(doc, jsonBuffer); // print to client

  bool published = client.publish(AWS_IOT_PUBLISH_TOPIC, jsonBuffer);
  if(published) {
    Serial.println("Publish Success");
  }
  else {
    Serial.println("Publish fail");
  }
}

void init_aht25(void) {
  delay(100);
  Wire.beginTransmission(AHT25_ADDR);
  Wire.write(0x71);
  Wire.endTransmission();
  delay(10);
  
  crc.setPolynome(0x31);
  crc.setStartXOR(0xFF);
}

void update_aht25(void) {
  byte buf[7];
  uint32_t humidity_raw;
  uint32_t temperature_raw;
  byte state;
  
  Wire.beginTransmission(AHT25_ADDR);
  Wire.write(0xAC);
  Wire.write(0x33);
  Wire.write(0x00);
  Wire.endTransmission();
  do {
    delay_with_client_loop(80);
    Wire.requestFrom(AHT25_ADDR, 7);
    if (Wire.available() >= 7) {
      for(int i=0; i<7; i++) {
        buf[i] = Wire.read();
      }
    }
  } while((buf[0] & 0x80) != 0);

  crc.restart();
  crc.add(buf, 6);

  if(buf[6] == crc.getCRC()) {
    state = buf[0];
    humidity_raw = ((uint32_t)buf[1] << 12)|((uint32_t)buf[2] << 4)|(((uint32_t)buf[3] >> 4) & 0x0F);
    temperature_raw = (((uint32_t)buf[3] & 0x0F) << 16)|((uint32_t)buf[4] << 8)|((uint32_t)buf[5]);
    
    humidity = humidity_raw / 1048576.0 * 100;
    temperature = temperature_raw / 1048576.0 * 200 - 50;
  }
  else {
    // error
    humidity = 999;
    temperature = 999;
  }

  Serial.print("temperature: ");
  Serial.println(temperature);
  Serial.print("humidity: ");
  Serial.println(humidity);
}

void update_perticle(void) {
  uint32_t adc;
  uint32_t val = 0;
  
  for(int i=0; i < 10; i++) {
    digitalWrite(PARTICLE_LED_PIN, HIGH);
    delayMicroseconds(280);
    adc = analogReadMilliVolts(PARTICLE_INPUT_PIN) * 2;
    delayMicroseconds(30); // 40us - ADC Conversion time(10us)
    digitalWrite(PARTICLE_LED_PIN, LOW);

    val += adc;
    Serial.print(adc);
    Serial.print(", ");
  
    delay_with_client_loop(10);
  }

  particle = val / 10;
  Serial.print("particle: ");
  Serial.println(particle);
}

void showLcd(void) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(temperature, 1);
  lcd.print("\xdf" "C"); // ゚C
  lcd.setCursor(0, 1);
  lcd.print(humidity, 0);
  lcd.print("% ");
  lcd.print(particle);
}

void setup() {
  Serial.begin(115200);
  Wire.begin(I2C_SDA, I2C_SCL);
  lcd.begin(8, 2);
  init_aht25();
  // for particle sensor
  pinMode(PARTICLE_LED_PIN, OUTPUT);
  
  WiFi.mode(WIFI_STA);
  WiFi.setAutoConnect(false);
}

void loop() {
  update_perticle();
  update_aht25();

  connectWiFi();
  connectAWS();
  publishMessage();
  showLcd();
  
  delay_with_client_loop(60 * 1000);
}

このほか、同一ディレクトリにSecrets.hという名前でWi-Fiの設定情報、IoT Coreの秘密鍵などを保存します。(秘密鍵の取得等は今後ご紹介します)

Secrets.h

#define SECRET
#define THINGNAME "ESP32AirSensor"

const char WIFI_SSID[] = "WIFI SSID";
const char WIFI_PASSWORD[] = "WIFI PASSWORD";
const char AWS_IOT_ENDPOINT[] = "xxxxxxxxx.iot.ap-northeast-1.amazonaws.com";

// Amazon Root CA 1
static const char AWS_CERT_CA[] PROGMEM = R"EOF(
-----BEGIN CERTIFICATE-----
Root CA
-----END CERTIFICATE-----
)EOF";

// Device Certificate
static const char AWS_CERT_CRT[] PROGMEM = R"KEY(
-----BEGIN CERTIFICATE-----
デバイス証明書
-----END CERTIFICATE-----
)KEY";

// Device Private Key
static const char AWS_CERT_PRIVATE[] PROGMEM = R"KEY(
-----BEGIN RSA PRIVATE KEY-----
デバイス秘密鍵
-----END RSA PRIVATE KEY-----
)KEY";

次回

ESP32をAWSに接続してみた(1) ハードウェアの製作

参考にしたページ