ガイド · 読了まで19分

ESP32でTelegramボットを作る:通知、LED、リレーの制御

ESP32でTelegramボットを作ります。BotFatherのトークン、チャットID、UniversalTelegramBotの設定、人感センサーとガスの通知、コマンドでのリレー制御、自分にだけ返信する設定まで解説します。

ESP32のTelegramボットを使うと、自宅のESP32ボードからスマホにメッセージを送ったり、スマホから送ったコマンドをボードに実行させたりできます。玄関で動きがあったときや台所でガスを検知したときには、すぐにポケットの中のスマホに通知が届きます。「/ac」(トルコ語で「オン」の意味)と送ればリレーでランプがつき、「/kapat」(「オフ」)と送れば消えます。スマホアプリを自作する必要も、ルーターのポートを開ける必要も、クラウドサービスにお金を払う必要もありません。Telegramの無料のBot APIと、ESP32に内蔵されたWi-Fiだけで実現できます。

このガイドでは、実際に動くシステムを一から作ります。BotFatherでボットを作ってトークンを取得し、自分のチャットIDを調べ、UniversalTelegramBotとArduinoJsonのライブラリをインストールして、ESP32から最初のメッセージを送ります。続いて、PIR人感センサーとMQ-2ガスセンサーによる動きとガスの通知、そしてTelegramのコマンドによるLEDとリレーの制御を加えます。さらに、ボットが自分のIDにだけ返信するようにする方法、証明書の設定、ESP8266とRaspberry Pi Pico Wでの違い、よくあるエラーも1つずつ取り上げます。

Telegramボットとして動くESP32開発ボード
Wi-Fiを内蔵しているので、ESP32はTelegramのサーバーに直接つながります。追加のモジュールやゲートウェイは必要ありません。

ESP32のTelegramボットとは?どのように動くのか

Telegramのボットとは、人ではなくプログラムが動かす特別なアカウントです。このプロジェクトでは、そのプログラムをESP32が動かします。ESP32とスマホが直接つながることはなく、どちらもTelegramのサーバーとやり取りします。スマホでボットに「/ac」と送ると、そのメッセージはサーバーで待機します。ESP32は1秒ごとにapi.telegram.orgへ暗号化されたHTTPSリクエスト(getUpdates)を送って「新しいメッセージはある?」と問い合わせ、メッセージを受け取って実行します。通知を送るときは、逆方向にsendMessageリクエストを送ります。接続を開くのは常にESP32の側なので、ルーターのポート転送や固定IPは必要ありません。インターネットにつながる2.4 GHzのWi-Fiネットワークなら、どこでもボードが動きます。

用語役割例
BotFatherボットを作成・管理するためのTelegramのボット/newbot、/setcommands、/revoke
ボットのトークンボットのパスワード。コードはこれを使ってTelegramに接続する1234567890:AAE...(数字、コロン、長い文字列)
チャットIDメッセージを送る相手(個人またはグループ)の番号個人:123456789、グループ:-1001234567890
getUpdatesボットに届いた新しいメッセージをサーバーから取得するbot.getUpdates(...)
sendMessage指定したチャットIDにメッセージを送るbot.sendMessage(CHAT_ID, "Hello")
コマンド/で始まる、ボットが理解できる短いメッセージ/ac、/kapat、/durum

なぜTelegramなのか?ほかの通知方法との比較

方法長所短所向いている用途
Telegramボット無料、即時通知、双方向のコマンド、ポート開放が不要グラフや既製のダッシュボードがないアラームと簡単な遠隔操作
Blynk IoT既製のボタン、メーター、グラフのダッシュボード無料プランではメッセージ数とデバイス数に制限があるセンサー監視のダッシュボード
メール(SMTP)どのスマホでも使え、長いレポートも送れる届くのが遅く、コマンドを受け取りにくい毎日のレポート
SMS(SIM800L)Wi-Fiもインターネットも不要SIMカードとSMSの料金、追加のモジュールが必要インターネットのない山小屋や物置
ESP32のWebサーバー完全にローカルで無料通常は同じネットワークからしか使えず、通知もない家の中の操作パネル

まとめると、「何かが起きたら知らせてほしい、コマンドを送ったらこれをしてほしい」というタイプのプロジェクトには、Telegramがいちばん簡単で安上がりな選択肢です。センサーのデータをグラフで見たいなら、Blynk×ESP32入門のほうが合っているかもしれません。Wi-Fiのない場所から通知を受けたいなら、SIM800L GSMモジュールのガイドを見てください(日本ではGSM(2G)のサービスがないため、実際には技適を取得したLTEモジュールが必要です)。

ステップ1:BotFatherでボットを作り、トークンを取得する

  1. スマホかパソコンでTelegramを開き、検索欄にBotFatherと入力して、名前の横に青いチェックマークが付いた公式アカウントを選びます。似た名前の偽アカウントに注意してください。
  2. Start(開始)をタップし、/newbotコマンドを送ります。
  3. BotFatherはまず、ボットの表示名を尋ねてきます。「自宅アラーム」など、好きな名前を入力してください。
  4. 次に、ユーザー名を尋ねられます。ユーザー名はほかと重複せず、半角英字、数字、アンダースコアだけを使い、最後がbotで終わる必要があります(例:my_home_alarm_bot)。すでに使われている場合は、末尾に数字を付けてもう一度試してください。
  5. BotFatherから「Done! Congratulations on your new bot」(完了しました。新しいボットの作成おめでとうございます)というメッセージと、ボットへのリンク、そしてトークンが届きます。トークンは1234567890:AAE...のような形で、数字、コロン、長い文字列からできています。全体をコピーして、安全な場所に保管してください。
  6. BotFatherから届いたリンクをタップしてボットを開き、Startを押します。この手順は欠かせません。Telegramでは、ボットからユーザーに最初のメッセージを送ることはできないので、会話は必ず自分から始める必要があります。
トークンはボットの鍵です。トークンを手に入れた人は誰でも、ボットに送られたメッセージを読んだり、ボットの名前でメッセージを送ったり、ESP32宛てのコマンドをESP32より先に横取りしたりできます。トークンを含むコードは、フォーラムやスクリーンショット、公開のGitHubリポジトリで絶対に共有しないでください。漏れたと思ったら、BotFatherで/revokeコマンドを使って古いトークンを無効にし、新しいトークンを取得して、コードを書き込み直してください。

ステップ2:TelegramのチャットIDを調べる方法

ボットのトークンが「どのボットか」を表すのに対し、チャットIDは「誰にメッセージを送るか」を表します。Telegramのユーザー、グループ、チャンネルにはそれぞれ変わらない数字のIDがあり、これは@で始まるユーザー名とは別物です。自分のチャットIDを調べる簡単な方法は3つあります。

  1. IDボットを使う:Telegramで@userinfobotなどのIDボットを探して、Startを押します。Idの行に自分の番号が送られてきます。これらのボットは自分のボットではなく、IDを表示するだけです。
  2. ブラウザを使う:まず、自分のボットに何かメッセージを送ります。次に、パソコンのブラウザでapi.telegram.org/botTOKEN/getUpdatesを開きます(TOKENの部分に自分のトークンを貼り付け、その前の「bot」という文字は残します)。表示されたテキストの中の"chat":{"id":123456789にある数字が、チャットIDです。結果のリストが空なら、もう一度ボットにメッセージを送ってから、ページを再読み込みしてください。
  3. ESP32を使う:下のコマンド用のコードを書き込むと、許可されていない人のIDが、シリアルモニタの「unauthorized ID」(許可されていないID)の行に表示されます。家族を追加したいときは、ボットにメッセージを送ってもらい、そこで番号を確認してください。
家族のグループでボットを使う場合は、ボットをグループに追加してメッセージを投稿し、もう一度getUpdatesのページを確認します。グループのIDはマイナス記号で始まり、スーパーグループのIDは-100で始まります。マイナス記号の付け忘れが、「chat not found」(チャットが見つからない)エラーのいちばん多い原因です。初期設定のプライバシーモードでは、ボットはグループの会話を読まず、自分宛ての/で始まるコマンドだけを受け取ります。コマンドで動くボットなら、それで十分です。

ステップ3:UniversalTelegramBotとArduinoJsonをインストールする

  1. Arduino IDEにまだESP32のボードパッケージを入れていない場合は、先にインストールしてください。手順はESP32とは?にあります。
  2. 「ツール」>「ライブラリを管理」を開き、検索欄にUniversalTelegramBotと入力して、Brian Loughのライブラリをインストールします。このガイドのコードは、UniversalTelegramBot 1.3.0、ArduinoJson 7、ESP32ボードパッケージのバージョン2.0.11でコンパイルしました。
  3. 依存ライブラリについて聞かれたら、全てをインストールを選びます。聞かれなかった場合は、同じ画面でArduinoJsonを検索し、Benoit Blanchonのライブラリを別にインストールしてください。
  4. 「ファイル」>「スケッチ例」>「UniversalTelegramBot」>「ESP32」には、FlashLEDなどの既製の例があります。このガイドのコードは、それらを簡略化し、セキュリティの対策を加えたものです。

UniversalTelegramBotはArduinoJsonのバージョン6の書き方で作られていますが、ArduinoJson 7でも問題なくコンパイルできます。Arduino IDEでコンパイラの警告をオンにしていると、「'DynamicJsonDocument' is deprecated: use JsonDocument instead」(非推奨なのでJsonDocumentを使う)のような行が表示されます。これはエラーではなく警告なので、プログラムは普通に動きます。ライブラリのインストールでつまずいた場合は、Arduinoにライブラリを追加する方法を確認してください。

部品と配線

プロジェクト全体には、ESP32開発ボード、HC-SR501 PIR人感センサー、MQ-2ガスセンサーモジュール、5 Vのリレーモジュール、10 kΩと20 kΩの抵抗を1本ずつ、ブレッドボード、ジャンパーワイヤーが必要です。LEDには、GPIO 2にあるESP32内蔵の青色LEDを使います。最初の2つのステップを試すだけなら、ESP32とUSBケーブルがあれば十分です。センサーはあとから追加できます。

ESP32
ESP32
PIR HC-SR501
PIR HC-SR501
MQ-2
MQ-2
リレーモジュール
リレーモジュール
接続ESP32ESP8266(NodeMCU)Raspberry Pi Pico W
PIR VCCVIN(5V)VIN(5V)VBUS(5V)
PIR OUTGPIO 27D5(GPIO 14)GP16
MQ-2 VCCVIN(5V)VIN(5V)VBUS(5V)
MQ-2 AO(10 kΩ / 20 kΩの分圧回路を経由)GPIO 34A0GP26(ADC0)
リレーのVCCVIN(5V)VIN(5V)VBUS(5V)
リレーのINGPIO 26D1(GPIO 5)GP15
LEDGPIO 2(内蔵)D4(内蔵、LOWで点灯)"LED"(内蔵)
すべてのGNDGNDGNDGND
ESP32、ESP8266、Picoのピンは3.3 Vで動作します。MQ-2モジュールを5 Vで動かすと、AO出力が5 Vまで上がることがあるので、分圧回路を組みます(値は分圧計算ツールで確認できます)。AOを10 kΩを通してアナログピンにつなぎ、アナログピンを20 kΩを通してGNDにつなぎます。これで5 Vが約3.3 Vまで下がります。リレーで230 Vの商用電源の負荷を切り替える場合は(日本の商用電源は100V(50/60Hz)です)、必ずプラグを抜いた状態で配線し、大人に手伝ってもらってください。詳しくはリレーモジュールとは?にあります。

電源にも注意してください。MQ-2のヒーターだけで約150 mA、リレーのコイルで約70 mAを消費し、ESP32はWi-Fiで送信するときに一瞬だけ最大250 mAに達することがあります。パソコンのUSBポートではこの合計をいつもまかなえるとは限らず、メッセージを送る瞬間にボードが再起動することがあります。1 A以上の5 V ACアダプターと品質のよいケーブルを使ってください。それでも問題が続く場合は、ESP32のブラウンアウトとGuru Meditationを読んでください。ボードのすべてのピンはピン配置ツールで確認できます。

最初のテスト:ESP32からTelegramにメッセージを送る

センサーに進む前に、いちばんシンプルなスケッチでトークンとチャットIDを確認しましょう。下のコードのWi-Fi名、パスワード、BOT_TOKEN、CHAT_IDの行を自分の情報に書き換えて、ボードに書き込みます。次に、シリアルモニタを115200ボーで開きます。

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5
6const char* WIFI_SSID = "AgAdi";          // Wi-Fi adın (yalnız 2,4 GHz) / your Wi-Fi name (2.4 GHz only)
7const char* WIFI_PASS = "Sifre123";       // Wi-Fi şifren / your Wi-Fi password
8#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"   // BotFather'ın verdiği token / token from BotFather
9#define CHAT_ID   "123456789"             // kendi Telegram ID'n / your own Telegram ID
10
11WiFiClientSecure client;                  // şifreli (HTTPS) bağlantı / encrypted (HTTPS) connection
12UniversalTelegramBot bot(BOT_TOKEN, client);
13
14void setup() {
15  Serial.begin(115200);
16  WiFi.mode(WIFI_STA);
17  WiFi.begin(WIFI_SSID, WIFI_PASS);
18  Serial.print("Wi-Fi'ye baglaniyor / connecting to Wi-Fi");
19  while (WiFi.status() != WL_CONNECTED) {
20    delay(500);
21    Serial.print(".");
22  }
23  Serial.println();
24  Serial.println(WiFi.localIP());
25
26  client.setCACert(TELEGRAM_CERTIFICATE_ROOT);   // api.telegram.org kök sertifikası / root certificate of api.telegram.org
27
28  if (bot.sendMessage(CHAT_ID, "Merhaba! ESP32 çevrimiçi / Hello! ESP32 is online")) {
29    Serial.println("Mesaj gonderildi / message sent");
30  } else {
31    Serial.println("Mesaj gonderilemedi / message failed");
32  }
33}
34
35void loop() {
36}

数秒以内に、ボットからTelegramに「Hello! ESP32 is online」(ESP32が起動しました)というメッセージが届き、シリアルモニタに「Mesaj gonderildi / message sent」(トルコ語と英語で「送信しました」)と表示されるはずです。sendMessage()はメッセージが届くとtrueを返します。「message failed」(送信失敗)と表示された場合、原因はWi-Fi接続、トークン、チャットID、証明書の設定のどれかです。コードを疑う前に、ブラウザでapi.telegram.org/botTOKEN/getMeを開いてみてください。トークンが正しければ"ok":trueとボットの名前が、間違っていれば「Unauthorized」(認証失敗)が表示されます。

証明書の設定:setCACertとsetInsecureのどちらを使う?

TelegramはHTTPSしか受け付けないため、コードでは普通のWiFiClientではなくWiFiClientSecureを使います。暗号化された接続では、ESP32は通信相手のサーバーが本当にapi.telegram.orgであることを確かめなければなりません。そのために、証明書チェーンの最上位にあるルート証明書を使って、サーバーの証明書を検証します。UniversalTelegramBotには、このルート証明書がTELEGRAM_CERTIFICATE_ROOTとして、すぐに使える形で入っています。Telegramの証明書チェーンは、2037年末まで有効なGo Daddy Root Certificate Authority - G2までさかのぼります。Telegramが証明書の発行元を変えた場合は、ライブラリも更新が必要になります。

設定役割安全性使う場面
client.setCACert(TELEGRAM_CERTIFICATE_ROOT)接続を暗号化し、サーバーの身元を確認する高推奨の設定。常にこれを使う
client.setInsecure()接続は暗号化するが、サーバーの身元は確認しない中:間に入り込んだ偽のサーバーに気づけない証明書の問題かどうかを切り分けるための簡単なテスト
どちらも使わない現在のESP32コアでは、接続がまったく確立されない動作しない使わない:メッセージは送られず、シリアルモニタにもエラーが出ない
ESP8266:setTrustAnchors(&cert)ESP8266での同等の設定。証明書はX509Listとして渡す高NodeMCUとWemos D1 Mini。正しい時刻が必要

Telegramのコマンド(/ac、/kapat)でLEDとリレーを制御する

次は、ボットを双方向にしましょう。下のコードは1秒に1回、Telegramに新しいメッセージを問い合わせます。テキストが/ac(トルコ語で「オン」)ならリレーとLEDをオンにし、/kapat(「オフ」)ならオフにし、/durum(「状態」)なら現在の状態を報告し、/startならコマンドの一覧を送ります。コマンド名は/onや/offに変えてもかまいませんが、Telegramのコマンドに使えるのは半角の英小文字、数字、アンダースコアだけで、長さは32文字までという点に注意してください。「/aç」のようなアクセント付きの文字を含むコマンドは、タップできるコマンドとして認識されません。日本語の文字も、同じくコマンドには使えません。

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5
6const char* WIFI_SSID = "AgAdi";
7const char* WIFI_PASS = "Sifre123";
8#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
9#define CHAT_ID   "123456789"                 // yalnız bu ID'ye yanıt verilir / only this ID gets answers
10
11const int LED_PIN  = 2;                       // çoğu DevKit'te dahili LED / onboard LED on most DevKits
12const int ROLE_PIN = 26;                      // röle modülünün IN pini / relay module IN pin
13const bool ROLE_AKTIF_LOW = true;             // çoğu modül LOW'da çeker / most modules switch on with LOW
14
15const unsigned long KONTROL_ARALIGI = 1000;   // Telegram'a saniyede bir sor / ask Telegram once per second
16unsigned long sonKontrol = 0;
17bool roleAcik = false;
18
19WiFiClientSecure client;
20UniversalTelegramBot bot(BOT_TOKEN, client);
21
22void roleYaz(bool acik) {
23  roleAcik = acik;
24  if (ROLE_AKTIF_LOW) digitalWrite(ROLE_PIN, acik ? LOW : HIGH);
25  else                digitalWrite(ROLE_PIN, acik ? HIGH : LOW);
26  digitalWrite(LED_PIN, acik ? HIGH : LOW);   // LED röle durumunu gösterir / LED mirrors the relay
27}
28
29void mesajlariIsle(int adet) {
30  for (int i = 0; i < adet; i++) {
31    String chatId = bot.messages[i].chat_id;
32    String metin  = bot.messages[i].text;
33
34    // Güvenlik: tanımadığın kişiye cevap verme / security: never answer strangers
35    if (chatId != CHAT_ID) {
36      Serial.println("Yetkisiz ID / unauthorized ID: " + chatId);
37      continue;
38    }
39
40    int at = metin.indexOf('@');              // gruptaki "/ac@bot_adi" -> "/ac" / "/ac@bot_name" in groups -> "/ac"
41    if (at > 0) metin = metin.substring(0, at);
42
43    if (metin == "/start") {
44      String yardim = "Merhaba " + bot.messages[i].from_name + "!\n";
45      yardim += "/ac - röleyi aç / relay ON\n";
46      yardim += "/kapat - röleyi kapat / relay OFF\n";
47      yardim += "/durum - son durum / status";
48      bot.sendMessage(chatId, yardim);
49    } else if (metin == "/ac") {
50      roleYaz(true);
51      bot.sendMessage(chatId, "Röle AÇIK / relay ON");
52    } else if (metin == "/kapat") {
53      roleYaz(false);
54      bot.sendMessage(chatId, "Röle KAPALI / relay OFF");
55    } else if (metin == "/durum") {
56      bot.sendMessage(chatId, roleAcik ? "Röle AÇIK / relay ON" : "Röle KAPALI / relay OFF");
57    } else {
58      bot.sendMessage(chatId, "Bilinmeyen komut, /start yaz / unknown command, send /start");
59    }
60  }
61}
62
63void setup() {
64  Serial.begin(115200);
65  pinMode(LED_PIN, OUTPUT);
66  pinMode(ROLE_PIN, OUTPUT);
67  roleYaz(false);                             // açılışta röle kapalı / relay off at boot
68
69  WiFi.mode(WIFI_STA);
70  WiFi.begin(WIFI_SSID, WIFI_PASS);
71  while (WiFi.status() != WL_CONNECTED) delay(500);
72  Serial.println(WiFi.localIP());
73
74  client.setCACert(TELEGRAM_CERTIFICATE_ROOT);
75  bot.sendMessage(CHAT_ID, "Bot hazır, /start yaz / bot ready, send /start");
76}
77
78void loop() {
79  if (millis() - sonKontrol > KONTROL_ARALIGI) {
80    int adet = bot.getUpdates(bot.last_message_received + 1);   // yeni mesaj var mı? / any new messages?
81    while (adet) {
82      mesajlariIsle(adet);
83      adet = bot.getUpdates(bot.last_message_received + 1);
84    }
85    sonKontrol = millis();
86  }
87}
行役割
bot.getUpdates(bot.last_message_received + 1)最後に処理したメッセージより後のメッセージを要求し、届いた件数を返す。+1で同じメッセージが戻ってこないようにする
bot.messages[i].chat_idメッセージが届いたチャットのID。返信はここに送られ、セキュリティチェックにも使う
bot.messages[i].text(本文)届いたテキスト。これとコマンドを比較する
metin.indexOf('@')グループでTelegramがコマンドに付け足す@bot_nameの部分を切り取る
ROLE_AKTIF_LOW多くのリレーモジュールはLOW信号でオンになる。HIGHでオンになるモジュールならfalseにする
KONTROL_ARALIGI問い合わせの間隔。1000 msなら反応が速く、3000〜5000 msならネットワークへの負荷が少ない

セキュリティ:自分のIDにだけ返信させる

Telegramのボットのユーザー名は公開されていて、検索もできます。誰かがmy_home_alarm_botを推測したり、スクリーンショットで見つけたりして/acを送ると、チェックのないESP32はそのコマンドにも従ってしまいます。だからこそ、コードでいちばん大事な行はchatId != CHAT_IDのチェックです。チャットIDは秘密にしておく必要はありません。守りの仕組みは、Telegram自身が各メッセージの送信者を確認していることにあり、誰も他人のIDを偽ることはできません。知らない相手にまったく返信しないのも、意図的な選択です。ボットが黙っている限り、動いていることさえ誰にもわかりません。

  • 許可リストを使う:許可するIDを配列にまとめ、それ以外のメッセージはすべて無視して、家族を1人ずつ追加します。
  • グループでは人を確認する:グループではチャットIDがグループのIDになります。特定の人だけがコマンドを送れるようにしたいなら、chat_idではなくfrom_idを比較します。
  • トークンは別のファイルに:トークンとWi-Fiのパスワードはsecrets.hのようなファイルに入れ、コードを共有するときはそのファイルを除外します。
  • 古いコマンドは飛ばす:停電中に送った/acが、ボードが復帰したときに思わぬタイミングで実行されてはいけません。完全版のコードでは、起動時のクリーンアップがこれを防ぎます。
  • 起動時は安全な状態に:ボードが起動するたびに、リレーがオフの状態から始めます。ヒーターやポンプのような負荷では、これがとても重要です。
  • 重要な仕事をボットだけに任せない:ドアロックや給湯器のようなものでは、間違ったコマンドで何が起こりうるかを考え、確認メッセージや時間制限を加えてください。

人感とガスのアラーム:Telegramに通知を送る完全版プロジェクト

では、これまでの部品を組み合わせましょう。下のコードは、PIRセンサーが動きを検知したときと、MQ-2のガスの値がしきい値を超えたときに、時刻付きの通知をTelegramで送ります。同時に/ac、/kapat、/alarm_ac、/alarm_kapat(アラームのオン/オフ)、/durumのコマンドを待ち受け、/startと入力すると画面下にタップで送れるボタンのメニューを表示し、コマンドをTelegramの「Menu」ボタン(メニュー)にも追加します。各センサーの詳細は、PIR人感センサーとMQ-2ガスセンサーのガイドを見てください。

C++ (Arduino IDE)
1#include <WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5#include <time.h>
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
10#define CHAT_ID   "123456789"                  // bildirimlerin gideceği ID / the ID that receives alerts
11
12// Komut verebilecek kişiler; aile üyesi eklemek için: { CHAT_ID, "222222222" }
13// People allowed to send commands; to add a family member: { CHAT_ID, "222222222" }
14const char* YETKILI[] = { CHAT_ID };
15const int YETKILI_SAYISI = sizeof(YETKILI) / sizeof(YETKILI[0]);
16
17const int LED_PIN  = 2;                        // dahili LED / onboard LED
18const int ROLE_PIN = 26;                       // röle modülü IN / relay module IN
19const int PIR_PIN  = 27;                       // PIR sensörü OUT / PIR sensor OUT
20const int GAZ_PIN  = 34;                       // MQ-2 AO, gerilim bölücü üzerinden / MQ-2 AO through a voltage divider
21const bool ROLE_AKTIF_LOW = true;              // çoğu modül LOW'da çeker / most modules switch on with LOW
22
23const int GAZ_ESIK = 1800;                     // temiz havadaki değere göre ayarla / tune it from your clean-air reading
24const int GAZ_FARK = 200;                      // histerezis payı / hysteresis margin
25const unsigned long HAREKET_BEKLEME = 60000;   // iki hareket bildirimi arası en az 60 sn / at least 60 s between motion alerts
26const unsigned long GAZ_ISINMA = 120000;       // MQ-2 ilk 2 dakika ısınır / MQ-2 warms up for the first 2 minutes
27const unsigned long KONTROL_ARALIGI = 1000;    // Telegram'a saniyede bir sor / poll Telegram once per second
28
29WiFiClientSecure client;
30UniversalTelegramBot bot(BOT_TOKEN, client);
31
32bool alarmKurulu = true;                       // hareket alarmı açık mı / is the motion alarm armed
33bool roleAcik = false;
34bool gazVar = false;
35unsigned long sonKontrol = 0;
36unsigned long sonHareket = 0;
37
38String saat() {                                // "14:05:31" biçiminde saat / time as "14:05:31"
39  struct tm t;
40  if (!getLocalTime(&t, 100)) return "--:--:--";
41  char buf[9];
42  strftime(buf, sizeof(buf), "%H:%M:%S", &t);
43  return String(buf);
44}
45
46bool yetkiliMi(const String& id) {
47  for (int i = 0; i < YETKILI_SAYISI; i++) {
48    if (id == YETKILI[i]) return true;
49  }
50  return false;
51}
52
53void roleYaz(bool acik) {
54  roleAcik = acik;
55  if (ROLE_AKTIF_LOW) digitalWrite(ROLE_PIN, acik ? LOW : HIGH);
56  else                digitalWrite(ROLE_PIN, acik ? HIGH : LOW);
57}
58
59String durumMetni() {
60  String s = "Röle / relay: ";
61  s += roleAcik ? "AÇIK / ON" : "KAPALI / OFF";
62  s += "\nAlarm: ";
63  s += alarmKurulu ? "KURULU / ARMED" : "KAPALI / DISARMED";
64  s += "\nGaz / gas: " + String(analogRead(GAZ_PIN));
65  s += "\nWi-Fi: " + String(WiFi.RSSI()) + " dBm";
66  return s;
67}
68
69void mesajlariIsle(int adet) {
70  for (int i = 0; i < adet; i++) {
71    String chatId = bot.messages[i].chat_id;
72    String metin  = bot.messages[i].text;
73
74    if (!yetkiliMi(chatId)) {                  // yabancıya sessiz kal / stay silent to strangers
75      Serial.println("Yetkisiz ID / unauthorized ID: " + chatId);
76      continue;
77    }
78    int at = metin.indexOf('@');               // "/ac@bot_adi" -> "/ac"
79    if (at > 0) metin = metin.substring(0, at);
80
81    if (metin == "/ac") {
82      roleYaz(true);
83      bot.sendMessage(chatId, "Röle AÇIK / relay ON");
84    } else if (metin == "/kapat") {
85      roleYaz(false);
86      bot.sendMessage(chatId, "Röle KAPALI / relay OFF");
87    } else if (metin == "/alarm_ac") {
88      alarmKurulu = true;
89      bot.sendMessage(chatId, "Hareket alarmı kuruldu / motion alarm armed");
90    } else if (metin == "/alarm_kapat") {
91      alarmKurulu = false;
92      bot.sendMessage(chatId, "Hareket alarmı kapatıldı / motion alarm disarmed");
93    } else if (metin == "/durum") {
94      bot.sendMessage(chatId, durumMetni());
95    } else {                                   // /start ve diğer her şey: tuş menüsü / /start and anything else: button menu
96      String klavye = "[[\"/ac\",\"/kapat\"],[\"/alarm_ac\",\"/alarm_kapat\"],[\"/durum\"]]";
97      bot.sendMessageWithReplyKeyboard(chatId, "Komut seç / choose a command", "", klavye, true);
98    }
99  }
100}
101
102void setup() {
103  Serial.begin(115200);
104  pinMode(LED_PIN, OUTPUT);
105  pinMode(ROLE_PIN, OUTPUT);
106  pinMode(PIR_PIN, INPUT);
107  roleYaz(false);                              // açılışta röle kapalı / relay off at boot
108
109  WiFi.mode(WIFI_STA);
110  WiFi.begin(WIFI_SSID, WIFI_PASS);
111  while (WiFi.status() != WL_CONNECTED) {
112    delay(500);
113    Serial.print(".");
114  }
115  Serial.println(WiFi.localIP());
116
117  client.setCACert(TELEGRAM_CERTIFICATE_ROOT); // api.telegram.org kök sertifikası / root certificate of api.telegram.org
118  bot.maxMessageLength = 4000;                 // büyük mesajlarda takılmayı önler / prevents hanging on big messages
119  configTime(3 * 3600, 0, "pool.ntp.org");     // Türkiye UTC+3; kendi saat dilimini yaz / Turkey is UTC+3, set your own zone
120
121  // Telegram'daki Menü düğmesi; komutlar küçük harf, Türkçe karakter yok
122  // The Menu button in Telegram; commands are lowercase with no special letters
123  bot.setMyCommands("[{\"command\":\"ac\",\"description\":\"Röleyi aç\"},"
124                    "{\"command\":\"kapat\",\"description\":\"Röleyi kapat\"},"
125                    "{\"command\":\"alarm_ac\",\"description\":\"Hareket alarmını kur\"},"
126                    "{\"command\":\"alarm_kapat\",\"description\":\"Hareket alarmını kapat\"},"
127                    "{\"command\":\"durum\",\"description\":\"Durumu göster\"}]");
128
129  // Kart kapalıyken gelen eski komutları atla / skip old commands sent while the board was off
130  while (bot.getUpdates(bot.last_message_received + 1)) { }
131
132  bot.sendMessage(CHAT_ID, "Sistem açıldı / system started " + saat());
133}
134
135void loop() {
136  unsigned long simdi = millis();
137
138  // 1) Hareket alarmı / motion alarm
139  if (alarmKurulu && digitalRead(PIR_PIN) == HIGH && simdi - sonHareket > HAREKET_BEKLEME) {
140    sonHareket = simdi;
141    digitalWrite(LED_PIN, HIGH);
142    bot.sendMessage(CHAT_ID, "Hareket algılandı! / motion detected! " + saat());
143    digitalWrite(LED_PIN, LOW);
144  }
145
146  // 2) Gaz alarmı: ısınmadan sonra, susturulamaz / gas alarm: after warm-up, cannot be muted
147  if (simdi > GAZ_ISINMA) {
148    int gaz = analogRead(GAZ_PIN);             // 0-4095
149    if (!gazVar && gaz > GAZ_ESIK) {
150      gazVar = true;
151      bot.sendMessage(CHAT_ID, "GAZ ALARMI! / GAS ALARM! " + String(gaz) + " " + saat());
152    } else if (gazVar && gaz < GAZ_ESIK - GAZ_FARK) {
153      gazVar = false;
154      bot.sendMessage(CHAT_ID, "Gaz normale döndü / gas back to normal: " + String(gaz));
155    }
156  }
157
158  // 3) Telegram komutları / Telegram commands
159  if (simdi - sonKontrol > KONTROL_ARALIGI && WiFi.status() == WL_CONNECTED) {
160    int adet = bot.getUpdates(bot.last_message_received + 1);
161    while (adet) {
162      mesajlariIsle(adet);
163      adet = bot.getUpdates(bot.last_message_received + 1);
164    }
165    sonKontrol = millis();
166  }
167}
設定値説明
GAZ_ESIK18000〜4095の生のADC値。きれいな空気での読み取り値をもとに設定する
GAZ_FARK200「正常に戻りました」の通知を送る前に、値がしきい値からこの分だけ下がる必要がある。境界付近で通知が何度も切り替わるのを防ぐ
HAREKET_BEKLEME60000 ms2回の動きの通知の間を、最低60秒空ける
GAZ_ISINMA120000 msMQ-2は予熱中に高い値を示すため、最初の2分間はガスをチェックしない
KONTROL_ARALIGI1000 msTelegramに新しいメッセージを問い合わせる間隔
bot.maxMessageLength4000ライブラリが読み取る応答の最大長。初期値は1500

コードの2つのアラームは、あえて異なる動きをするようにしています。人感アラームはクールダウンで守られています。部屋の中を人が歩き回っている間、PIRセンサーは何度も反応するので、クールダウンがないとスマホが1分間に何十件ものメッセージであふれてしまいます。クールダウンのタイマーはゼロから始まるので、最初の通知が届くのはボードの起動から早くても60秒後です。これは、PIRセンサーが電源投入後に安定するまでにかかる約1分ともちょうど合います。一方、ガスアラームではヒステリシスを使っています。値がしきい値を超えたときに1回だけ警告し、しきい値より200下がって初めて「正常に戻りました」と知らせます。また、安全のため/alarm_kapatでは止められません。Telegram側も、あまり頻繁なメッセージは許可していません。同じチャットに1秒に1件より多く、またはグループに1分に20件より多く送ろうとすると、「Too Many Requests: retry after」(リクエストが多すぎるので時間をおいて再試行)エラーになります。

GAZ_ESIKは推測ではなく、測って決めてください。きれいな空気の中でボードを5〜10分動かし、Telegramで/durumを送って、返ってきたガスの値を記録します。しきい値は、その値より数百高く設定します。bot.longPoll変数も知っておくと便利です。20のような値にすると、getUpdatesは新しいメッセージを最大20秒待つので、コマンドにはすぐ返信されますが、その間loop()は止まったままになり、センサーは読まれません。アラームのプロジェクトでは、longPollは0のままにしておいてください。
MQ-2は学習・趣味向けのセンサーで、検定を受けたガス検知器ではありません。また、Wi-Fiやインターネットが止まっているときは通知が届きません。家庭のガス漏れ対策として、これだけに頼ることは絶対にしないでください。検定に合格した市販のガス警報器を設置し、このプロジェクトはその補助の警報システムとして使いましょう。

ESP8266(NodeMCU)でTelegramボットを作る

同じライブラリはESP8266 NodeMCUやWemos D1 Miniのボードでも動きます。コマンドとアラームのロジックはまったく同じで、変わるのは接続の部分だけです。WiFi.hの代わりにESP8266WiFi.hを使い、ルート証明書をX509ListとしてsetTrustAnchors()に渡します。また、ESP8266は証明書の有効期間をチェックするので、接続の前にNTPで時刻を取得する必要があります。時刻がないと、すべての接続がエラーも出さずに失敗します。NodeMCUの内蔵LEDはD4ピンにあってLOWで点灯し、アナログ入力はA0の1つだけです。

C++ (Arduino IDE)
1#include <ESP8266WiFi.h>
2#include <WiFiClientSecure.h>
3#include <UniversalTelegramBot.h>
4#include <ArduinoJson.h>
5#include <time.h>
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9#define BOT_TOKEN "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
10#define CHAT_ID   "123456789"
11
12X509List cert(TELEGRAM_CERTIFICATE_ROOT);      // ESP8266'da kök sertifika böyle verilir / how ESP8266 takes the root certificate
13WiFiClientSecure client;
14UniversalTelegramBot bot(BOT_TOKEN, client);
15
16void setup() {
17  Serial.begin(115200);
18  configTime(0, 0, "pool.ntp.org");            // sertifika kontrolü için saat şart / the clock is needed to check the certificate
19  client.setTrustAnchors(&cert);
20
21  WiFi.mode(WIFI_STA);
22  WiFi.begin(WIFI_SSID, WIFI_PASS);
23  while (WiFi.status() != WL_CONNECTED) delay(500);
24
25  time_t simdi = time(nullptr);                // NTP saatini bekle / wait for the NTP time
26  while (simdi < 24 * 3600) {
27    delay(100);
28    simdi = time(nullptr);
29  }
30  bot.sendMessage(CHAT_ID, "ESP8266 çevrimiçi / ESP8266 is online");
31}
32
33void loop() {
34}

Raspberry Pi Pico WとMicroPythonでTelegramボットを作る

Raspberry Pi Pico Wには、別のライブラリは必要ありません。MicroPythonのrequestsモジュールで、Telegram Bot APIに直接リクエストを送れます。下のコードは、動きを検知すると通知を送り、/acと/kapatのコマンドでGP15のリレーとボードの内蔵LEDを切り替え、自分のチャットIDにだけ返信します。ThonnyでPico Wに最新のMicroPythonを書き込み、コードをmain.pyとして保存してください。MicroPythonのrequestsモジュールは接続を暗号化しますが、サーバー証明書は検証しないので、安全性のレベルはESP32のsetInsecure()と同じです。

MicroPython
1import network, time
2import requests
3from machine import Pin
4
5WIFI_SSID = "AgAdi"                    # Wi-Fi adın / your Wi-Fi name
6WIFI_PASS = "Sifre123"                 # Wi-Fi şifren / your Wi-Fi password
7TOKEN = "1234567890:AAExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
8CHAT_ID = 123456789                    # sayı olarak yaz / write it as a number
9API = "https://api.telegram.org/bot" + TOKEN
10
11led = Pin("LED", Pin.OUT)              # Pico W dahili LED / Pico W onboard LED
12role = Pin(15, Pin.OUT, value=1)       # aktif LOW röle kapalı başlar / active-LOW relay starts off
13pir = Pin(16, Pin.IN)                  # PIR OUT
14
15wlan = network.WLAN(network.STA_IF)
16wlan.active(True)
17wlan.connect(WIFI_SSID, WIFI_PASS)
18while not wlan.isconnected():
19    time.sleep(0.5)
20print("IP:", wlan.ifconfig()[0])
21
22def gonder(metin):
23    try:
24        r = requests.post(API + "/sendMessage", json={"chat_id": CHAT_ID, "text": metin})
25        r.close()
26    except Exception as e:
27        print("Gonderilemedi / send failed:", e)
28
29def yeni_mesajlar(offset):
30    try:
31        r = requests.get(API + "/getUpdates?timeout=0&limit=5&offset=%d" % offset)
32        sonuc = r.json().get("result", [])
33        r.close()
34        return sonuc
35    except Exception as e:
36        print("Okunamadi / read failed:", e)
37        return []
38
39# Kart kapalıyken gelen eski komutları atla / skip old commands sent while the board was off
40offset = 0
41eski = yeni_mesajlar(offset)
42while eski:
43    offset = eski[-1]["update_id"] + 1
44    eski = yeni_mesajlar(offset)
45
46gonder("Pico W çevrimiçi / Pico W is online")
47son_hareket = time.ticks_ms()          # ilk 60 sn PIR dengelenir / the PIR settles in the first 60 s
48
49while True:
50    if pir.value() and time.ticks_diff(time.ticks_ms(), son_hareket) > 60000:
51        son_hareket = time.ticks_ms()
52        gonder("Hareket algılandı! / motion detected!")
53
54    for u in yeni_mesajlar(offset):
55        offset = u["update_id"] + 1
56        mesaj = u.get("message", {})
57        if mesaj.get("chat", {}).get("id") != CHAT_ID:     # yalnız sana yanıt ver / answer only you
58            continue
59        komut = mesaj.get("text", "").split("@")[0]
60        if komut == "/ac":
61            role.value(0)              # aktif LOW: 0 = açık / active LOW: 0 = on
62            led.on()
63            gonder("Röle AÇIK / relay ON")
64        elif komut == "/kapat":
65            role.value(1)
66            led.off()
67            gonder("Röle KAPALI / relay OFF")
68        elif komut == "/durum":
69            gonder("Röle / relay: " + ("AÇIK / ON" if role.value() == 0 else "KAPALI / OFF"))
70    time.sleep(1)

ESP32のTelegramボットのプロジェクトアイデア

  • ドア・窓のアラーム:リードスイッチやPIRセンサーを使い、誰もいない家で動きがあったらスマホにメッセージを送ります。
  • 写真付きの防犯カメラ:動きを検知したらESP32-CAMで写真を撮り、Telegramに送ります。手順はESP32-CAMとTelegramのガイドにあります。
  • 部屋や温室のモニター:DHT11で測った温度が設定値を超えたら警告を受け取り、/durumで現在の値を問い合わせます。
  • 植物の水やりリマインダー:土壌水分センサーが乾燥を示したら通知を受け取り、/sula(トルコ語で「水をやる」)のようなコマンドでポンプを数秒間動かします。
  • 火災の警告:炎センサーとMQ-2を組み合わせて、両方の危険を同時に見張ります。
  • 電池で動く郵便受けセンサー:ESP32をディープスリープにしておき、ふたが開いたら起こして、メッセージを1件送ってからまたスリープに戻します。
  • 遠隔操作のコンセント:リレーを通して、卓上ランプや扇風機を世界中のどこからでも/acと/kapatで切り替えます。

ESP32のTelegramボットでよくあるエラーと対処法

症状またはメッセージ原因対処
sendMessageがfalseを返し、シリアルモニタにエラーが出ないsetCACertかsetInsecureの行がない、トークンに空白が入っている、またはWi-Fiにつながっていない証明書の行を追加し、トークンをコピーし直し、まずWi-Fi接続を確認する
ブラウザでのテストで「Forbidden: bot can't initiate conversation with a user」(ボットからは会話を始められない)ボットでStartを押していない、またはボットをブロックしているTelegramでボットを開いてStartを押す。ブロックしていたら解除する
エラー:「Bad Request: chat not found」(チャットが見つからない)チャットIDの間違い、グループIDのマイナス記号の付け忘れ、またはボットがグループから削除されたgetUpdatesでIDを確認し直し、グループIDはマイナス記号付きで書く
エラー:「Unauthorized」(認証失敗)トークンが間違っているか、/revokeで変更されたBotFatherから現在のトークンをコピーし、コードを書き込み直す
メッセージは送れるが、コマンドに反応しないloop()内の長いdelay()、チャットIDの間違い、またはグループでコマンドに@bot_nameが付いている待ち時間にはmillis()を使い、@以降を切り取り、シリアルモニタの「unauthorized ID」の行を確認する
写真やスタンプのあと、ボットがまったく反応しなくなったライブラリは応答の最初の1500文字しか読まないため、長い応答を解析できず、同じメッセージが何度も戻ってくるsetup()にbot.maxMessageLength = 4000;の行を追加する
起動時にボードが古いコマンドを実行するTelegramは未読のメッセージを最大24時間保持する起動時に保留中のメッセージを読み取って破棄する
エラー:「Conflict: terminated by other getUpdates request」(ほかのgetUpdatesと競合)同じトークンが2台のデバイスで同時に、またはパソコンのスクリプトでも動いているデバイスごとにBotFatherで別のボットを作る
エラー:「Too Many Requests: retry after 30」(送信が多すぎる)メッセージの送信間隔が短すぎるクールダウンを加え、同じチャットへの送信は1秒に1件までにする
コンパイル時に「ArduinoJson.h: No such file or directory」(ファイルが見つからない)ArduinoJsonライブラリがインストールされていないライブラリマネージャからArduinoJsonをインストールする
メッセージの送信中にボードが再起動するWi-Fi送信時の電流の急増に電源が耐えられない(ブラウンアウト)1 AのACアダプター、短く品質のよいケーブル、3V3とGNDの間に100〜470 µFのコンデンサ
トラブルシューティングは順番を守って進めましょう。まずシリアルモニタでWi-Fiにつながっていることを確認し、次にブラウザでgetMeを使ってトークンを、sendMessageを使ってチャットIDをテストし、コードを見るのは最後にします。ブラウザでテストするには、api.telegram.org/botTOKEN/sendMessage?chat_id=123456789&text=testを開くだけです。エラーがあれば、Telegramがdescriptionの行にその理由をわかりやすい文章で示してくれます。Wi-Fiの段階でつまずいた場合は、ESP32がWi-Fiにつながらないときのガイドを確認してください。

トークンとチャットID、そして数行のコードだけで、ESP32は家の様子を報告し、コマンドを受け付ける小さなアシスタントになりました。同じ仕組みに温度、水位、ドアのセンサーを加えたり、通知を家族のグループに送ったりして、さらに広げることもできます。BlueGraysアプリには、PIR、ブザー、LEDでTelegramに通知を送るESP32の人感アラームシステム、MQ-2のガス・煙検知アラーム、ESP32のWi-Fiプロジェクトが、回路図とコード付きでステップごとに揃っています。

よくある質問

ESP32のTelegramボットは無料ですか?

はい。Telegramでのボットの作成、Bot APIの利用、メッセージの送信はすべて無料で、月ごとのメッセージ数の上限もありません。あるのは送信頻度の制限だけで、同じチャットへは1秒に約1件、グループへは1分に20件までです。家庭のプロジェクトなら、この制限で十分すぎるほどです。

TelegramのチャットIDはどうやって調べますか?

いちばん簡単なのは、Telegramで@userinfobotなどのIDボットを開いてStartを押す方法です。ID番号が送られてきます。または、自分のボットにメッセージを送り、ブラウザでapi.telegram.org/botTOKEN/getUpdatesを開いて、"chat"の部分にあるidの値を読み取ります。グループのIDはマイナス記号で始まります。

ESP32からTelegramにメッセージが送られないのはなぜですか?

よくある原因は、ボットでStartを押していない、チャットIDが間違っている、トークンに空白が入っている、コードにsetCACertかsetInsecureの行がない、のいずれかです。getMeとsendMessageのアドレスをブラウザで開けば、コードとは関係なくトークンとチャットIDをテストできます。また、ボードが2.4 GHzのWi-Fiネットワークにつながっていることも確認してください。

ほかの人にTelegramボットを操作されることはありますか?

ボットのユーザー名は公開されているので、コードにIDのチェックを入れていなければ、ボットを見つけた人は誰でもコマンドを送れてしまいます。届いたチャットIDを毎回自分のIDと比較し、それ以外を無視すれば、これを防げます。Telegramが送信者を確認しているからです。トークンも絶対に共有しないでください。漏れた場合は、BotFatherの/revokeで新しいものに替えてください。

ESP8266でもTelegramボットは作れますか?

はい。UniversalTelegramBotライブラリは、NodeMCUやWemos D1 MiniなどのESP8266ボードでも動きます。ルート証明書はX509ListとしてsetTrustAnchors()に渡し、接続の前にNTPで時刻を取得する必要があります。ESP8266はメモリが少ないので、長いメッセージはESP32のほうが余裕をもって扱えます。

ESP32からTelegramに写真を送れますか?

はい。ライブラリのsendPhotoByBinary()関数を使えば、ESP32-CAMで撮影した画像をそのままTelegramに送れます。カメラのないESP32でも、同じ方法でSDカードに保存した画像を送ったり、sendPhoto()でネット上の画像のアドレスを送ったりできます。動きを検知したら写真を送るシステムは、防犯プロジェクトの中でも特に人気があります。

全プロジェクトを見る
ESP32でLEDを点滅
#103初級

ESP32でLEDを点滅

ESP32のGPIO出力の基本を学びます。デジタルピンからLEDを点灯・消灯させて、点滅させます。Arduino UNOの定番のLED点滅プロジェクトのESP32版です。Arduino環境で使う基本的な点滅コマンドは同じですが、ESP32のピン配置と3.3 VのGPIO電圧レベルはArduino UNOとは異なります。

4.7(37)
99
ESP32の内蔵LEDを点滅
#106初級

ESP32の内蔵LEDを点滅

ESP32 DevKit V1に内蔵された青色LEDを点滅させます。外付け部品は不要です!ESP32を購入したら、最初に試すプロジェクトです。GPIO 2が内蔵LEDにつながっています。

4.8(29)
53
ESP32動体検知アラーム(PIR + ブザー + LED + Telegram)
#145上級
プレミアム

ESP32動体検知アラーム(PIR + ブザー + LED + Telegram)

PIRセンサーが動きを検知するとブザーとLEDを作動させ、同時にTelegramで持ち主に即座に通知するスマート防犯アラームです。アラームが繰り返される頻度を抑えるため、30秒のクールダウン(待ち時間)を設けています。この仕組みはPIRの検知が正しいかを確認したり、誤検知をなくしたりするものではありません。家の防犯、ガレージの監視、倉庫や作業部屋などに使えます。ESP32-CAMなら写真付きの通知を追加でき、通常のESP32 DevKit V1ではテキストで通知します。

0.0(0)
9
ESP32 MQ-2ガス・煙検知アラーム
#122中級

ESP32 MQ-2ガス・煙検知アラーム

このプロジェクトでは、MQ-2ガスセンサーモジュールで可燃性ガス(LPG、メタン、プロパン)と煙を検知し、パッシブブザーでサイレンのような警報音を鳴らします。センサーのしきい値判定用デジタル出力(DO)をESP32のGPIO 4ピンに接続します。ガスや煙がしきい値を超えるとDOがLOWになり、アラームが作動します。感度はモジュール上のポテンショメーターで調整します。パッシブブザーをtone()で駆動し、2つの周波数を交互に切り替えてサイレン音を作ります。

4.7(28)
43
ESP32スマート照明(PIR + LDR + リレー + OLED)
#141上級
プレミアム

ESP32スマート照明(PIR + LDR + リレー + OLED)

ESP32で廊下やトイレ、食品庫の照明をスマート化します!PIRモーションセンサー、LDR光センサー、リレーモジュールを使い、「暗い場所で動きを検知すると点灯する」定番の仕組みを作ります。周囲が明るければ動きを検知しても点灯しないため、日中の無駄な電力消費を抑えられます。照明は30秒間点灯し、動きが続くとタイマーがリセットされて点灯時間が延びます。SSD1306 OLEDには大きな文字で状態(ON / DAYTIME / WAITING)とカウントダウンを表示します。

4.3(6)
11
ESP32 PIR人感センサーによる自動照明
#121中級
プレミアム

ESP32 PIR人感センサーによる自動照明

このプロジェクトでは、HC-SR501 PIR人感センサーで部屋に入った人を検知し、LEDを自動で点灯させます。millis()で点灯時間を管理します。センサーの値がHIGHの間は最後に動きを検知した時刻を更新し、最後にHIGHを読み取ってから少なくとも10秒後にLEDを消灯します。現在のコードは各ループでdelay(50)により50 ms待機するため、完全にノンブロッキングではありません。階段や廊下、浴室、戸棚内の照明などに応用できます。

4.4(24)
38
ESP32 Wi-Fi STA接続(LCDディスプレイ付き)
#127上級

ESP32 Wi-Fi STA接続(LCDディスプレイ付き)

このプロジェクトでは、ESP32をステーション(STA)モードで家庭のWi-Fiネットワークに接続し、安全で障害に強い接続を実現します。SSIDとパスワードを別のsecrets.hファイルに保存してGitへの流出を防ぎ、自動再接続の仕組みを実装します。RSSIによる信号強度とIPアドレスはI2C LCDディスプレイに表示します。内蔵LEDは状態表示に使います。実運用を想定した接続管理の仕組みです。

4.4(21)
34