ESP32 C3省電力温度監視システム構築

(DS18B20 + Discord通知 + Ambient + Web設定)

システム概要

本記事では、ESP32(Seeeduino XIAO ESP32C3)を使用した
完全自動・超省電力温度監視システムの構築方法を紹介します。

農業用ハウス、倉庫、冷蔵設備などの遠隔温度監視を目的としたシステムです。


システムの特徴

  • DS18B20温度センサーによる温度測定
  • 測定時のみセンサーへ給電(省電力設計)
  • 設定温度超過時にDiscordへ自動通知
  • Ambientへ温度データを自動送信
  • DeepSleepによる長時間バッテリー駆動
  • スマホから設定できるWeb設定画面
  • 個体識別用MACアドレス管理

使用機材

  • マイコン:Seeeduino XIAO ESP32C3
  • 温度センサー:DS18B20 防水タイプ
  • 電源:18650 リチウムイオン電池
  • 抵抗:4.7kΩ(プルアップ)、220Ω(LED)
  • ケース:防水ケース
  • その他:LED、設定ボタン

回路構成

回路図


回路のポイント

  • DS18B20のDATAラインに4.7kΩプルアップ抵抗
  • センサー電源をGPIOで制御(省電力)
  • LEDで動作状態表示
  • 設定ボタンでWeb設定モード起動
#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Discord_WebHook.h>
#include <Ambient.h>
#include <esp_sleep.h>
#include <esp_system.h>

#define CONFIG_BUTTON_PIN 10
#define LED_PIN 5

#define SENSOR_POWER_PIN 20
#define ONE_WIRE_PIN 4

Preferences preferences;
WebServer server(80);
WiFiClient client;
Discord_Webhook discord;
Ambient ambient;

OneWire oneWire(ONE_WIRE_PIN);
DallasTemperature sensors(&oneWire);

// ===== 設定値 =====
String kotaiID = "5";
float thresholdTemp = 28.0;
unsigned long sleepSec = 300;
String WIFI_SSID = "SSID";
String WIFI_PASSWORD = "pass";
String ambientUserKey = "YourUserKey";
String DISCORD_WEBHOOK = "YourWEBHOOK";
bool ENABLE_DISCORD = true;

// 表示&devKey用(コロン無しMAC)
String MAC_ADDR;

void loadSettings() {
  preferences.begin("config", true);
  kotaiID         = preferences.getString("kotaiID", kotaiID);
  thresholdTemp   = preferences.getFloat("threshold", thresholdTemp);
  sleepSec        = preferences.getULong("sleep", sleepSec);
  WIFI_SSID       = preferences.getString("ssid", WIFI_SSID);
  WIFI_PASSWORD   = preferences.getString("pass", WIFI_PASSWORD);
  ambientUserKey  = preferences.getString("ambient", ambientUserKey);
  DISCORD_WEBHOOK = preferences.getString("discord", DISCORD_WEBHOOK);
  ENABLE_DISCORD  = preferences.getBool("enable_discord", ENABLE_DISCORD);
  preferences.end();
}

void saveSettings() {
  preferences.begin("config", false);
  preferences.putString("kotaiID", kotaiID);
  preferences.putFloat("threshold", thresholdTemp);
  preferences.putULong("sleep", sleepSec);
  preferences.putString("ssid", WIFI_SSID);
  preferences.putString("pass", WIFI_PASSWORD);
  preferences.putString("ambient", ambientUserKey);
  preferences.putString("discord", DISCORD_WEBHOOK);
  preferences.putBool("enable_discord", ENABLE_DISCORD);
  preferences.end();
}

float measureTempC() {
  digitalWrite(SENSOR_POWER_PIN, HIGH);
  delay(80);

  sensors.begin();
  sensors.requestTemperatures();
  delay(750);

  float t = sensors.getTempCByIndex(0);

  digitalWrite(SENSOR_POWER_PIN, LOW);
  return t;
}

bool connectWiFi(uint32_t timeoutMs = 10000) {
  // まれな不安定対策:一度OFF→STA
  WiFi.mode(WIFI_OFF);
  delay(50);
  WiFi.mode(WIFI_STA);

  WiFi.begin(WIFI_SSID.c_str(), WIFI_PASSWORD.c_str());

  uint32_t start = millis();
  while (WiFi.status() != WL_CONNECTED && (millis() - start) < timeoutMs) {
    delay(100);
  }
  return (WiFi.status() == WL_CONNECTED);
}

void wifiOff() {
  WiFi.disconnect(false);
  WiFi.mode(WIFI_OFF);
}

void goSleep() {
  esp_sleep_enable_timer_wakeup(sleepSec * 1000000ULL);
  esp_deep_sleep_start();
}

// ===== 設定画面(MACもコロン無し表示) =====
void handleRoot() {
  String html = "<!DOCTYPE html><html><head><meta charset='UTF-8'>"
                "<meta name='viewport' content='width=device-width, initial-scale=1'>"
                "<style>body{font-family:sans-serif;padding:20px;background:#f9f9f9}"
                "form{background:#fff;padding:20px;border-radius:10px;max-width:600px}"
                ".row{display:flex;align-items:center;margin-bottom:12px}"
                ".row label{width:180px;font-weight:bold}"
                ".row input{width:240px;padding:6px}</style></head><body>";

  html += "<h2>ESP32温度センサー設定</h2><form method='POST' action='/save'>";
  html += "<div class='row'><label>MAC</label><div>" + MAC_ADDR + "</div></div>";
  html += "<div class='row'><label>個体ID</label><input name='id' value='" + kotaiID + "'></div>";
  html += "<div class='row'><label>閾値(℃)</label><input name='threshold' type='number' step='0.1' value='" + String(thresholdTemp) + "'></div>";
  html += "<div class='row'><label>測定間隔(秒)</label><input name='sleep' type='number' value='" + String(sleepSec) + "'></div>";
  html += "<div class='row'><label>WiFi SSID</label><input name='ssid' value='" + WIFI_SSID + "'></div>";
  html += "<div class='row'><label>WiFi PASS</label><input name='pass' type='password' value='" + WIFI_PASSWORD + "'></div>";
  html += "<div class='row'><label>Ambient UserKey</label><input name='ambient' value='" + ambientUserKey + "'></div>";
  html += "<div class='row'><label>Discord Webhook</label><input name='discord' value='" + DISCORD_WEBHOOK + "'></div>";
  html += "<div class='row'><label>Discord有効</label><input name='enable_discord' type='checkbox' value='1'";
  if (ENABLE_DISCORD) html += " checked";
  html += "></div>";
  html += "<input type='submit' value='保存'></form></body></html>";

  server.send(200, "text/html", html);
}

void handleSave() {
  kotaiID        = server.arg("id");
  thresholdTemp  = server.arg("threshold").toFloat();
  sleepSec       = server.arg("sleep").toInt();
  WIFI_SSID      = server.arg("ssid");
  WIFI_PASSWORD  = server.arg("pass");
  ambientUserKey = server.arg("ambient");
  DISCORD_WEBHOOK= server.arg("discord");
  ENABLE_DISCORD = server.hasArg("enable_discord");

  saveSettings();

server.send(200, "text/html; charset=UTF-8",
            "<!DOCTYPE html><html><head><meta charset='UTF-8'>"
            "<meta name='viewport' content='width=device-width, initial-scale=1'>"
            "<style>"
            "body{font-family:sans-serif;text-align:center;padding:40px;}"
            ".msg{font-size:32px;font-weight:bold;}"
            "</style>"
            "</head><body>"
            "<div class='msg'>保存しました。<br>再起動します...</div>"
            "</body></html>");

  delay(500);
  ESP.restart();
}

void startConfigPortal() {
  digitalWrite(LED_PIN, HIGH);

  // 複数台でも混乱しないAP名(MAC末尾)
  String apSsid = "ESP32_Config_" + MAC_ADDR.substring(8); // 例: AABBCCDD
  WiFi.mode(WIFI_AP);
  WiFi.softAP(apSsid.c_str());

  server.on("/", handleRoot);
  server.on("/save", HTTP_POST, handleSave);
  server.begin();

  Serial.print("設定モード開始 AP SSID: ");
  Serial.println(apSsid);
  Serial.print("IP: ");
  Serial.println(WiFi.softAPIP());

  while (true) {
    server.handleClient();
    delay(10);
  }
}

// ===== メイン =====
void setup() {
  Serial.begin(115200);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  pinMode(CONFIG_BUTTON_PIN, INPUT_PULLUP);
  pinMode(SENSOR_POWER_PIN, OUTPUT);
  digitalWrite(SENSOR_POWER_PIN, LOW);

  // ★ MAC(esp_read_mac / コロン無し)
  uint8_t mac[6];
  esp_read_mac(mac, ESP_MAC_WIFI_STA);
  char macStr[13];
  sprintf(macStr, "%02X%02X%02X%02X%02X%02X",
          mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  MAC_ADDR = String(macStr);

  Serial.print("MAC: ");
  Serial.println(MAC_ADDR);

  loadSettings();

  if (digitalRead(CONFIG_BUTTON_PIN) == LOW) {
    Serial.println("設定ボタン押下 → 設定モードへ");
    startConfigPortal();
  }

  // 温度計測(省電力:センサー電源ONはこの瞬間だけ)
  float temperature = measureTempC();
  Serial.print("計測温度: ");
  Serial.print(temperature);
  Serial.println(" ℃");

  bool sendToDiscord = false;
  String msg;

  if (temperature == -127.0) {
    msg = "⚠️ DS18B20 read failed. Temp: -127.0°C";
    sendToDiscord = true;
  } else if (temperature > thresholdTemp) {
    msg = "🔥 異常温度: " + String(temperature) + "°C (ID:" + kotaiID + ")";
    sendToDiscord = true;
  }

  // Wi-Fi接続(必要なときだけON)
  Serial.print("WiFi接続: ");
  bool wifiOK = connectWiFi(10000);
  Serial.println(wifiOK ? "OK" : "NG");

  // ★ WiFi詳細表示
  if (wifiOK) {
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
    Serial.print("RSSI: ");
    Serial.println(WiFi.RSSI());
  }

  if (wifiOK) {
    // Discord
    if (ENABLE_DISCORD && sendToDiscord && DISCORD_WEBHOOK.length() > 0) {
      Serial.print("Discord送信: ");
      Serial.println(msg);
      discord.begin(DISCORD_WEBHOOK);
      discord.send(msg);
    } else {
      Serial.println("Discord送信: なし");
    }

    // Ambient
    if (ambientUserKey.length() > 0) {
      char devKey[32], writeKey[20];

      strncpy(devKey, MAC_ADDR.c_str(), sizeof(devKey) - 1);
      devKey[sizeof(devKey) - 1] = '\0';

      unsigned int channelId;
      Serial.print("Ambient送信: ");

      if (!ambient.getchannel(ambientUserKey.c_str(), devKey, channelId, writeKey, sizeof(writeKey), &client)) {
        Serial.println("NG(チャンネル情報取得失敗)");
        Serial.print("devKey=");
        Serial.println(devKey);
      } else {
        writeKey[sizeof(writeKey) - 1] = '\0';
        for (int i = 0; i < (int)sizeof(writeKey); i++) {
          if (writeKey[i] == '}' || writeKey[i] == '"') writeKey[i] = '\0';
        }

        ambient.begin(channelId, writeKey, &client);
        ambient.set(1, temperature);

        Serial.print("channelId=");
        Serial.println(channelId);
        Serial.print("writeKey=");
        Serial.println(writeKey);

        Serial.println(ambient.send() ? "OK" : "NG(送信失敗)");
      }
    } else {
      Serial.println("Ambient送信: なし(UserKey未設定)");
    }
  }

  wifiOff();
  digitalWrite(LED_PIN, LOW);

  goSleep();
}

void loop() {}

実機構成

防水ケース内部構成

  • ESP32基板固定
  • 18650バッテリー内蔵
  • センサーケーブル引き出し
  • 動作LED確認窓
  • 防水DS18B20温度プローブ
  • ハウス内や倉庫へ設置可能
温度センサーケース入り

動作フロー

起動後、以下の流れで自動動作します。

  • 起動
  • 設定ボタン押下判定
    • 押されていれば設定Web画面起動
  • 温度測定(センサー通電)
  • WiFi接続
  • 異常判定
    • 異常あり → Discord通知
    • 正常 → 記録のみ
  • Ambient送信
  • WiFi OFF
  • DeepSleep

Web設定画面

設定ボタンを押しながら電源を入れると
ESP32がアクセスポイントとして起動します。

SSID例:

ESP32_Config_XXXXXX

スマホから接続し、以下の設定が可能です。


設定画面

ESP32C3設定画面
  • MACアドレス(個体識別)
  • 個体ID
  • 温度閾値
  • 測定間隔
  • WiFi設定
  • Ambient UserKey
  • Discord Webhook
  • Discord通知ON/OFF

保存完了画面

設定保存

保存後、自動的に再起動します。


省電力設計

本システムは長期バッテリー駆動を前提に設計しています。

省電力のポイント

  • WiFiは通信時のみON
  • センサーは測定時のみ通電
  • CPUはDeepSleep
  • 平均消費電流は数十μA

18650バッテリーで数ヶ月運用が可能です。


Discord異常通知

以下の場合に自動通知されます。

  • 温度が設定値を超過した場合
  • センサーエラー発生時

通知例:


Ambientデータ送信

  • MACアドレスをdevKeyとして使用
  • 個体ごとに自動チャンネル作成
  • 温度データを時系列で記録

活用用途

  • ビニールハウス温度監視
  • 冷蔵庫温度監視
  • 倉庫温度管理
  • 設備異常検知
  • 農業IoT
  • 食品管理

まとめ

このシステムにより

  • 完全自動温度監視
  • 超省電力運用
  • 遠隔通知
  • データ記録
  • Web設定

をすべて実現できます。

農業・設備管理・防災用途など幅広く応用可能なIoT温度監視システムです。

  • このエントリーをはてなブックマークに追加
  • follow us in feedly

この記事の著者

momo

1966年訓子府町生まれの訓子府育ち。玉葱や米、メロンを栽培する農家です。一眼レフを本格的に始めたのは2005年。仕事の時でもいつでもカメラを持ち歩く自称農場カメラマン。普段の生活を撮るのが主で、その他ストロボを使っての商品撮影、スタジオ撮影も。愛好家グループで年1回写真展を行っている。農機具の改造や作製、電子工作など、モノづくりが大好きです。

この著者の最新の記事

関連記事

コメント

  1. この記事へのコメントはありません。

  1. この記事へのトラックバックはありません。

2026年1月
 1234
567891011
12131415161718
19202122232425
262728293031  

カテゴリー

ページ上部へ戻る