(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
スマホから接続し、以下の設定が可能です。
設定画面

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

保存後、自動的に再起動します。
省電力設計
本システムは長期バッテリー駆動を前提に設計しています。
省電力のポイント
- WiFiは通信時のみON
- センサーは測定時のみ通電
- CPUはDeepSleep
- 平均消費電流は数十μA
18650バッテリーで数ヶ月運用が可能です。
Discord異常通知
以下の場合に自動通知されます。
- 温度が設定値を超過した場合
- センサーエラー発生時
通知例:

Ambientデータ送信
- MACアドレスをdevKeyとして使用
- 個体ごとに自動チャンネル作成
- 温度データを時系列で記録
活用用途
- ビニールハウス温度監視
- 冷蔵庫温度監視
- 倉庫温度管理
- 設備異常検知
- 農業IoT
- 食品管理
まとめ
このシステムにより
- 完全自動温度監視
- 超省電力運用
- 遠隔通知
- データ記録
- Web設定
をすべて実現できます。
農業・設備管理・防災用途など幅広く応用可能なIoT温度監視システムです。
コメント
この記事へのトラックバックはありません。






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