ESP-WROOM-02で温度と二酸化炭素をセンシングして見える化してみた①

November 26, 2022

以前二酸化炭素を測定してociでグラフにする、という実験をやりましたが、さらに温度センサーを追加してグラフにすることにしました。今回はこちらのページを参考にさせてもらってLM61CIZを使用しました。また、温度センサーから出力される電圧値を読み込むため、こちらのページにあるESP-WROOM-02のTOUTピンを使いました。このピンは1Vまでしか入力できないので、抵抗を使って分圧してます。下の写真の一番上の真ん中左にあるのがLM61CIZです。

ブレッドボードに温度センサーを追加

あと、以前の回路ではFT232RLの3V3OUTピンからESP-WROOM-02に電圧を供給していたのですが、今回温度センサーも追加したためか不安定(起動したと思ったらすぐにリセットしてしまう)な状態になってしまった(こちらのページにあるようにこのピンが50mAまでしか対応していないためと思われる)ので、3.3Vの定電圧レギュレータを新たに追加してこちらから電圧を供給するように変更したところ、安定して起動するようになりました。上の写真でいうと中央の左下にある黒い四角の部品が定電圧レギュレータです。一応電圧を安定させるために電解コンデンサと積層セラミックコンデンサをかましています(参考ページ)。

ESP-WROOM-02に書き込むプログラムは以下のように実装しました。前回と異なるのは、MQTTに送るデータを暗号化するため、TLSを使用している点と、温度もあわせて送るようにしています(こちらのページを参考させてもらいました)。とりあえず暗号化だけでよかったので、クライアント証明書は送っていないです。また、測定した時間も一緒に送るために、setup_wifi関数でインターネット時間あわせしてます。

#include <Arduino_JSON.h>

#include <ESP.h>
#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <TimeLib.h> 

//#define ENABLE_DEBUG (1)


#define PWMPIN  (12)
#ifdef ENABLE_DEBUG
#define DPRINT(s) Serial.print(s)
#define DPRINTLN(s) Serial.println(s)
#else
#define DPRINT(s)
#define DPRINTLN(s)
#endif


static const char *SSID = "xxxxx";
static const char *PASSWORD = "xxxxxxx";


// MQTT Config
static const char *MQTT_SERVER = "mqtt.hostname.com";
static const char *MQTT_FINGERPRINT = "xx:xx:xx:....";
static const uint16_t MQTT_PORT = 8883u;
static const char *MQTT_PUB_TOPIC = "sensor";
static const char *MQTT_SUB_TOPIC = "airc";
static const char *MQTT_USER = "user";
static const char *MQTT_PASS = "1234";

unsigned long lastConnectionTime = 0; 
const unsigned long postingInterval = 20L * 1000L; // Post data every 20 seconds.

static BearSSL::WiFiClientSecure  wifiClient;
static PubSubClient client(wifiClient);

// WIFI
static void setup_wifi() {
  DPRINTLN(F("Connecting to access point ..."));
  WiFi.mode(WIFI_STA);
  WiFi.begin(SSID, PASSWORD);
  while(WiFi.status() != WL_CONNECTED) {
    delay(1000);
  }
  DPRINTLN(F("Connected."));
  configTime( 3600 * 9, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
  DPRINTLN(F("Sync time."));
  time_t t;
  do {
      t = time(NULL);
      delay(100);
  } while (t == 0);
}

// MQTT CLIENT
static void on_mqtt_message_received(const char *topic, byte *payload, unsigned int length) {
  
}

static void setup_mqtt(){  
  wifiClient.allowSelfSignedCerts();
  wifiClient.setFingerprint(MQTT_FINGERPRINT);
  wifiClient.setSSLVersion(BR_TLS11);
  client.setServer(MQTT_SERVER, MQTT_PORT);
  client.setCallback(on_mqtt_message_received);
  client.setKeepAlive( 90 );
  client.setBufferSize(256);
  client.setSocketTimeout(20);
}

static void mqtt_connect() {
  while(!client.connected()) {
    DPRINT(F("Attempting MQTT connection..."));
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    if(client.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) {
      DPRINTLN(F("MQTT connected"));
      delay(250);
    }
    else {
      DPRINT(F("Connection failed, rc="));
      DPRINT(client.state());
      DPRINTLN(F(" retry in 5sec."));
      delay(5000);
    }
  }
}

static void publish() {
  JSONVar obj;
  char buf[256];
  unsigned long pwmco2;
  int ans, av, tv;
  double temp;

  time_t t = time(nullptr);
  char snow[30];

  const char* format = "%04d-%02d-%02dT%02d:%02d:%02dZ";
  sprintf(snow, format, year(t), month(t), day(t), hour(t), minute(t), second(t));

  // 二酸化炭素濃度を計測
  pwmco2 = pulseIn(PWMPIN,HIGH,2000000); // パルスがHIGHになっている時間(マイクロ秒)
  obj["cd"] = 5 * (pwmco2 - 1200) / 1000; // ppmに変換

  // 温度を計測
  ans = analogRead(A0); // A0(0-1023)は1Vまでしか入力できないので10k,6.7kΩの抵抗で分圧している(MAX 1.6V->958mV)
  //av = ans * 487 / 432; // A0に入力された電圧(mV)
  //tv = 167 * av / 100; // 温度センサーのVout(mV)
  //temp = tv / 10 - 60; // 温度(℃)
  temp = (double)ans * 167.0 * 487.0 / 432.0 / 1000.0 - 60.0;
  obj["temp"] = temp;  
  obj["calc_at"] = snow;
  
  JSON.stringify(obj).toCharArray(buf, sizeof(buf));
  client.publish(MQTT_PUB_TOPIC, buf);
  // client.subscribe(MQTT_SUB_TOPIC);
  lastConnectionTime = millis();
}

// PUBLIC API
void setup() {
  Serial.begin(115200);
  pinMode( PWMPIN, INPUT );
#ifdef ENABLE_DEBUG
  Serial.setDebugOutput(true);
#endif
  delay(1000);
  setup_wifi();
  setup_mqtt();
}


void loop() {
  if(!client.connected()) {
    mqtt_connect();
  }
  client.loop();
  if (millis() - lastConnectionTime > postingInterval) 
  {
    publish();
    DPRINTLN(F("MQTT data sent"));
  }
}

次はMQTTブローカー側についてです。上述したようにTLSを使用していますので、MQTTサーバ側でルートCAやサーバの証明書を作成する必要があります。できるだけ楽をしたかったので今回はこちらで公開されているMakefileを使って証明書作成からサーバ立ち上げまでをほぼ全自動で行いました。もちろん、ドメイン名などの設定内容は少し変更しました。例えばクライアント証明書は使用しないようにするため、mosquitto.confのrequire_certificateはfalseに変更しました。

以上でセンサー値を含むメッセージを送る側(パブリッシャー)とMQTTブローカーの準備ができました。次の記事ではブローカーからメッセージを受け取ってDBに保存するサブスクライバーとDBの見える化する方法について記載します。