このプロジェクトでは、ESP-NOW 通信プロトコルを使用しながら、ESP8266 NodeMCU ボードを使用して Web サーバーをホストする方法を学習します。複数の ESP8266 ボードがセンサーの読み取り値を ESP-NOW 経由で 1 台の ESP8266 レシーバーに送信するように設定でき、Web サーバー上にすべての読み取り値が表示されます。ソフトウェア コードは Arduino IDE を使用してプログラムされます。
ESP-NOW と Wi-Fi を同時に使用する
Wi-Fi を使用して Web サーバーをホストし、ESP-NOW を使用して他のボードからセンサーの読み取り値を受信する場合は、考慮すべき点がいくつかあります。
- ESP8266 送信ボードは受信ボードと同じ Wi-Fi チャネルを使用する必要があります。
- 受信ボードの Wi-Fi チャンネルは、Wi-Fi ルーターによって自動的に割り当てられます。
- 受信ボードの Wi-Fi モードはアクセス ポイントおよびステーション ( WIFI_AP_STA ) である必要があります。
- 同じ Wi-Fi チャネルを手動で設定することも、送信機に簡単なコードを追加して、その Wi-Fi チャネルを受信ボードと同じ Wi-Fi チャネルに設定することもできます。
プロジェクト概要
下の画像は、構築するプロジェクトの概要を示しています。
- 2 つの ESP8266 送信ボードがあり、ESP-NOW (ESP-NOW 多対 1 構成) を通じて BME280 の温度と湿度の測定値を 1 つの ESP8266 受信ボードに送信します。
- ESP8266 受信ボードはパケットを受信し、測定値を Web サーバーに表示します。
- Web ページは、Server Sent Events (SSE) を使用して新しい読み取り値を受信するたびに自動的に更新されます。
- このページには、JavaScript を使用して測定値が最後に更新された時刻も表示されます。
前提条件
このプロジェクトを進める前に、次の前提条件を必ず確認してください。
開発環境
Arduino IDE を使用して ESP8266 ボードをプログラムするため、このチュートリアルを続ける前に、ESP8266 ボードが Arduino IDE にインストールされていることを確認してください。
- Arduino IDE に ESP8266 開発ボードをインストールします (Windows、Mac OS X、Linux)。
BME280ライブラリ
ESP8266 送信ボードは、BME280 センサーからの温度と湿度の測定値を送信します。
BME280 センサーからデータを読み取るには、 Adafruit_BME280 ライブラリを使用します。このライブラリを使用するには、 Adafruit Unified Sensor ライブラリもインストールする必要があります 。これらのライブラリをインストールするには、次の手順に従ってください。
検索ボックスで 「 adafruit bme280 」を検索し 、ライブラリをインストールします。
BME280 ライブラリを使用するには、Adafruit_Sensor ライブラリもインストールする必要があります。Arduino IDE にライブラリをインストールするには、次の手順に従います。
[スケッチ] > [ライブラリを含める] > [ライブラリの管理]に移動し 、検索ボックスに 「Adafruit Unified Sensor」と入力します 。下までスクロールしてライブラリを見つけてインストールします。
BME280 温度、湿度、圧力センサーの詳細については、ガイド「Arduino IDE を使用した ESP8266 および BME280 (圧力、温度、湿度)」を参照してください。
非同期Webサーバーライブラリ
Web サーバーを構築するには、次のライブラリをインストールする必要があります。
これらのライブラリは Arduino Library Manager を通じてインストールできないため、ライブラリ ファイルを Arduino Installation Libraries フォルダにコピーする必要があります。あるいは、Arduino IDE で [スケッチ] > [ライブラリをインクルード ] > [.zip ライブラリを追加]に移動し 、ダウンロードしたライブラリを選択することもできます。
Arduino_JSON ライブラリ
Arduino_JSON ライブラリをインストールする必要があります。このライブラリは、Arduino IDE ライブラリ マネージャーにインストールできます。[スケッチ] > [ライブラリを含める] > [ライブラリの管理]に移動し 、次のようにライブラリ名を検索します。
必要なハードウェア
このチュートリアルに従うには、複数の ESP8266 開発ボードが必要です。ESP8266 ボードを 3 枚使用します。以下も必要です。
- 3x ESP8266 (最高の ESP8266 開発ボードを読む)
- 2x BME280 センサー – ESP8266 用 BME280 ガイド
- ブレッドボード
- ジャンパー
受信ボードのMACアドレスを取得する
ESP-NOW 経由でメッセージを送信するには、受信ボードを知っている必要があります。各ボードには固有の MAC アドレスがあります。
次のコードを ESP8266 受信ボードにアップロードして、その MAC アドレスを取得します。
// 获取和更改ESP MAC地址的完整代码:
#ifdef ESP32
#include <WiFi.h>
#else
#include <ESP8266WiFi.h>
#endif
void setup(){
Serial.begin(115200);
Serial.println();
Serial.print("ESP Board MAC Address: ");
Serial.println(WiFi.macAddress());
}
void loop(){
}
コードをアップロードした後、RST/EN ボタンを押すと、MAC アドレスがシリアル モニターに表示されます。
ESP8266 レシーバー (ESP-NOW + Web サーバー)
ESP8266 NodeMCU 受信ボードは送信ボードからパケットを受信し、Web サーバーをホストして最新の受信値を表示します。
次のコードを受信ボードにアップロードします。このコードは、2 つの異なるボードから読み取り値を受信できるようになります。
#include <espnow.h>
#include <ESP8266WiFi.h>
#include "ESPAsyncWebServer.h"
#include "ESPAsyncTCP.h"
#include <Arduino_JSON.h>
// Replace with your network credentials (STATION)
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
int id;
float temp;
float hum;
unsigned int readingId;
} struct_message;
struct_message incomingReadings;
JSONVar board;
AsyncWebServer server(80);
AsyncEventSource events("/events");
// callback function that will be executed when data is received
void OnDataRecv(uint8_t * mac_addr, uint8_t *incomingData, uint8_t len) {
// Copies the sender mac address to a string
char macStr[18];
Serial.print("Packet received from: ");
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.println(macStr);
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
board["id"] = incomingReadings.id;
board["temperature"] = incomingReadings.temp;
board["humidity"] = incomingReadings.hum;
board["readingId"] = String(incomingReadings.readingId);
String jsonString = JSON.stringify(board);
events.send(jsonString.c_str(), "new_readings", millis());
Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
Serial.printf("t value: %4.2f \n", incomingReadings.temp);
Serial.printf("h value: %4.2f \n", incomingReadings.hum);
Serial.printf("readingID value: %d \n", incomingReadings.readingId);
Serial.println();
}
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP-NOW DASHBOARD</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h1 { font-size: 2rem;}
body { margin: 0;}
.topnav { overflow: hidden; background-color: #2f4468; color: white; font-size: 1.7rem; }
.content { padding: 20px; }
.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
.cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.reading { font-size: 2.8rem; }
.timestamp { color: #bebebe; font-size: 1rem; }
.card-title{ font-size: 1.2rem; font-weight : bold; }
.card.temperature { color: #B10F2E; }
.card.humidity { color: #50B8B4; }
</style>
</head>
<body>
<div class="topnav">
<h1>ESP-NOW DASHBOARD</h1>
</div>
<div class="content">
<div class="cards">
<div class="card temperature">
<p class="card-title"><i class="fas fa-thermometer-half"></i> BOARD #1 - TEMPERATURE</p><p><span class="reading"><span id="t1"></span> °C</span></p><p class="timestamp">Last Reading: <span id="rt1"></span></p>
</div>
<div class="card humidity">
<p class="card-title"><i class="fas fa-tint"></i> BOARD #1 - HUMIDITY</p><p><span class="reading"><span id="h1"></span> %</span></p><p class="timestamp">Last Reading: <span id="rh1"></span></p>
</div>
<div class="card temperature">
<p class="card-title"><i class="fas fa-thermometer-half"></i> BOARD #2 - TEMPERATURE</p><p><span class="reading"><span id="t2"></span> °C</span></p><p class="timestamp">Last Reading: <span id="rt2"></span></p>
</div>
<div class="card humidity">
<p class="card-title"><i class="fas fa-tint"></i> BOARD #2 - HUMIDITY</p><p><span class="reading"><span id="h2"></span> %</span></p><p class="timestamp">Last Reading: <span id="rh2"></span></p>
</div>
</div>
</div>
<script>
function getDateTime() {
var currentdate = new Date();
var datetime = currentdate.getDate() + "/"
+ (currentdate.getMonth()+1) + "/"
+ currentdate.getFullYear() + " at "
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds();
return datetime;
}
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('new_readings', function(e) {
console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
document.getElementById("rt"+obj.id).innerHTML = getDateTime();
document.getElementById("rh"+obj.id).innerHTML = getDateTime();
}, false);
}
</script>
</body>
</html>)rawliteral";
void setup() {
// Initialize Serial Monitor
Serial.begin(115200);
// Set the device as a Station and Soft Access Point simultaneously
WiFi.mode(WIFI_AP_STA);
// Set device as a Wi-Fi Station
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Setting as a Wi-Fi Station..");
}
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());
// Init ESP-NOW
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info
esp_now_register_recv_cb(OnDataRecv);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html);
});
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
});
server.addHandler(&events);
server.begin();
}
void loop() {
static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 5000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
events.send("ping",NULL,millis());
lastEventTime = millis();
}
}
コードの仕組み
まず、必要なライブラリをインクルードします。
#include <espnow.h>
#include <ESP8266WiFi.h>
#include "ESPAsyncWebServer.h"
#include "ESPAsyncTCP.h"
#include <Arduino_JSON.h>
各ボードから受信したデータを使用して JSON 変数を作成するため、Arduino_JSON ライブラリが必要です。このプロジェクトの後半で説明するように、この JSON 変数は、必要なすべての情報を Web ページに送信するために使用されます。ESP8266 がローカル ネットワークに接続できるように、次の行にネットワーク資格情報を挿入します。
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
データ構造
次に、受信するデータを含む構造体を作成します。ボード ID、温度と湿度の測定値、および読み取り ID が含まれるこの構造をメッセージと呼びます。
typedef struct struct_message {
int id;
float temp;
float hum;
int readingId;
} struct_message;
受信読み取りと呼ばれるメッセージ型の新しい変数構造を作成し、変数値を保存します。
struct_message incomingReadings;
「board」という名前のファイルを作成します。
JSONVar board;
ポート 80 に非同期 Web サーバーを作成します。
AsyncWebServer server(80);
イベントソースの作成
新しい測定値が到着したときに Web サーバー上の情報を自動的に表示するには、Server Sent Events (SSE) を使用します。
次の行では、新しいイベント ソースを作成します。
AsyncEventSource events("/events");
サーバー送信イベントにより、Web ページ (クライアント) はサーバーから更新を取得できます。これを使用して、新しい ESP-NOW パケットが到着したときに Web サーバー ページに新しい測定値を自動的に表示します。
OnDataRecv() 関数
このOnDataRecv()関数は、新しい ESP-NOW パケットを受信したときに実行されます。
void OnDataRecv(uint8_t * mac_addr, uint8_t *incomingData, uint8_t len) {
この関数内で、送信者の MAC アドレスを出力します。
char macStr[18];
Serial.print("Packet received from: ");
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.println(macStr);
入力データ変数内の情報をコピーし、読み取り構造変数に渡します。
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
次に、受け取った情報を使用して JSON 文字列変数を作成します。
board["id"] = incomingReadings.id;
board["temperature"] = incomingReadings.temp;
board["humidity"] = incomingReadings.hum;
board["readingId"] = String(incomingReadings.readingId);
String jsonString = JSON.stringify(board);
これは受信したJSON 文字列です
board = {
"id": "1",
"temperature": "24.32",
"humidity" = "65.85",
"readingId" = "2"
}
受信したすべてのJSON 文字列変数データを収集した後、この情報をイベント ( "new_readings" )としてブラウザーに送信します。
events.send(jsonString.c_str(), "new_readings", millis());
後で、クライアント側でこれらのイベントを処理する方法を見ていきます。
最後に、デバッグのために受信した情報を Arduino IDE シリアル モニターに出力します。
Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
Serial.printf("t value: %4.2f \n", incomingReadings.temp);
Serial.printf("h value: %4.2f \n", incomingReadings.hum);
Serial.printf("readingID value: %d \n", incomingReadings.readingId);
Serial.println();
Web ページを構築する
Index_html変数には、Web ページを構築するすべての HTML、CSS、および JavaScript が含まれています。HTML と CSS がどのように機能するかについては詳しく説明しません。サーバーから送信されたイベントを処理する方法についてのみ見ていきます。
イベントを処理する
新しいイベント ソースオブジェクトを作成し、更新の送信先ページの URL を指定します。私たちの場合、それはイベント ソースです。
if (!!window.EventSource) {
var source = new EventSource('/events');
イベント ソースをインスタンス化したら、 eventListener() を追加してサーバーからのメッセージの待機を開始できます。
これらは、AsyncWebServerドキュメントに示されているデフォルトのイベント リスナーです。
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
次に、イベントリスナー「new_readings」を追加します。
source.addEventListener('new_readings', function(e) {
ESP8266 が新しいパケットを受信すると、測定値を含む JSON 文字列をイベント ( 「new_readings」 ) としてクライアントに送信します。次の行は、ブラウザがこのイベントを受信したときに何が起こるかを処理します。
console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
document.getElementById("rt"+obj.id).innerHTML = getDateTime();
document.getElementById("rh"+obj.id).innerHTML = getDateTime();
基本的に、ブラウザコンソールに新しい測定値を出力し、受信したデータを Web ページ上の ID に対応する要素に置きます。また、 GetDateTime() JavaScript 関数を呼び出して、測定値を受信した日時も更新します。
function getDateTime() {
var currentdate = new Date();
var datetime = currentdate.getDate() + "/"
+ (currentdate.getMonth()+1) + "/"
+ currentdate.getFullYear() + " at "
+ currentdate.getHours() + ":"
+ currentdate.getMinutes() + ":"
+ currentdate.getSeconds();
return datetime;
}
設定()
setup()内で、ESP8266 レシーバーをアクセス ポイントおよび Wi-Fi ステーションとしてセットアップします。
WiFi.mode(WIFI_AP_STA);
次の行は ESP8266 をローカル ネットワークに接続し、IP アドレスと Wi-Fi チャネルを出力します。
// Set device as a Wi-Fi Station
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Setting as a Wi-Fi Station..");
}
Serial.print("Station IP Address: ");
Serial.println(WiFi.localIP());
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());
ESP-NOWを初期化します。
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
新しい ESP-NOW パケットが到着したときに実行するデータ受信コールバック関数を登録します。
esp_now_register_recv_cb(OnDataRecv);
リクエストを処理します
ESP8266 の IP アドレス/ URLに root でアクセスすると、 index_htmlに格納されている変数を送信してWeb ページを構築します。
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send_P(200, "text/html", index_html);
});
サーバーイベントソース
サーバー上にイベント ソースを設定します。
events.onConnect([](AsyncEventSourceClient *client){
if(client->lastId()){
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
);
server.addHandler(&events);
最後にサーバーを起動します。
server.begin();
ループ()
loop() 内で、ping が 5 秒ごとに送信されます。これは、サーバーがまだ実行中かどうかを確認するためにクライアント側で使用されます (このコードは必須ではありません)。
static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 5000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
events.send("ping",NULL,millis());
lastEventTime = millis();
}
以下の画像は、サーバー送信イベントがこのプロジェクトでどのように機能するか、および Web ページを更新せずに値を更新する方法をまとめています。
コードを受信ボードにアップロードした後、オンボードのEN/RSTボタンを押します。ESP8266 の IP アドレスは、シリアル モニターと Wi-Fi チャネルに印刷されている必要があります。
ESP8266ハードウェア接続
ESP8266 開発ボードは BME280 センサーに接続されています。センサーをデフォルトの ESP8266 I2C ピンに接続します。
- インターフェイス 5 (D1) -> SCL
- ポート4 (D2) -> SDA
ESP8266 取得ボード コード (ESP-NOW)
各取得ボードは、ESP-NOW プロトコルを介して、取得ボード ID (どのボードが測定値を送信したかを識別できるように)、温度、湿度、および測定値 ID を含むデータを JSON として送信します。読み取り ID は、送信されたメッセージの数を示す int 数値です。
次のコードを各収集ボードにアップロードします。各送信ボードの番号にIDを追加し、WIFI アカウントとパスワード情報を変更することを忘れないでください 。
/*
Rui Santos
Complete project details at https://RandomNerdTutorials.com/esp8266-esp-now-wi-fi-web-server/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
*/
#include <espnow.h>
#include <ESP8266WiFi.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
// Set your Board ID (ESP32 Sender #1 = BOARD_ID 1, ESP32 Sender #2 = BOARD_ID 2, etc)
#define BOARD_ID 2
Adafruit_BME280 bme;
//MAC Address of the receiver
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
//Structure example to send data
//Must match the receiver structure
typedef struct struct_message {
int id;
float temp;
float hum;
int readingId;
} struct_message;
//Create a struct_message called myData
struct_message myData;
unsigned long previousMillis = 0; // Stores last time temperature was published
const long interval = 10000; // Interval at which to publish sensor readings
unsigned int readingId = 0;
// Insert your SSID
constexpr char WIFI_SSID[] = "REPLACE_WITH_YOUR_SSID";
int32_t getWiFiChannel(const char *ssid) {
if (int32_t n = WiFi.scanNetworks()) {
for (uint8_t i=0; i<n; i++) {
if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
return WiFi.channel(i);
}
}
}
return 0;
}
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
}
float readTemperature() {
float t = bme.readTemperature();
return t;
}
float readHumidity() {
float h = bme.readHumidity();
return h;
}
// Callback when data is sent
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
Serial.print("Last Packet Send Status: ");
if (sendStatus == 0){
Serial.println("Delivery success");
}
else{
Serial.println("Delivery fail");
}
}
void setup() {
//Init Serial Monitor
Serial.begin(115200);
initBME();
// Set device as a Wi-Fi Station and set channel
WiFi.mode(WIFI_STA);
int32_t channel = getWiFiChannel(WIFI_SSID);
WiFi.printDiag(Serial); // Uncomment to verify channel number before
wifi_promiscuous_enable(1);
wifi_set_channel(channel);
wifi_promiscuous_enable(0);
WiFi.printDiag(Serial); // Uncomment to verify channel change after
// Init ESP-NOW
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for Send CB to
// get the status of Trasnmitted packet
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
esp_now_register_send_cb(OnDataSent);
esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
}
void loop() {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// Save the last time a new reading was published
previousMillis = currentMillis;
//Set values to send
myData.id = BOARD_ID;
myData.temp = readTemperature();
myData.hum = readHumidity();
myData.readingId = readingId++;
esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
Serial.print("loop");
}
}
コードの仕組み
まず必要なライブラリをインポートします。
#include <espnow.h>
#include <ESP8266WiFi.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
ボード番号を設定する
ESP8266 取得ボード ID を定義します。たとえば、ESP8266 Sender #1 にはBOARD_ID 1を設定します。
#define BOARD_ID 1
BME280センサー
bmeという名前のAdafruit_BME280オブジェクトを作成します。
Adafruit_BME280 bme;
受信者のMACアドレス
次の行に受信者の MAC アドレスを挿入します (たとえば)。
uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x15, 0xC7, 0xFC};
データ構造
次に、送信するデータを含む構造体を作成します。この構造化メッセージには、ボード ID、温度測定値、湿度測定値、および測定値 ID が含まれます。
typedef struct struct_message {
int id;
float temp;
float hum;
int readingId;
} struct_message;
変数の値を保存するために、mydataという新しい型の変数構造メッセージを作成します。
struct_message myData;
タイマー間隔
10 秒ごとに測定値を公開するための補助タイマー変数をいくつか作成します。遅延間隔を可変に変更できます。
unsigned long previousMillis = 0; // Stores last time temperature was published
const long interval = 10000; // Interval at which to publish sensor readings
読み取り値の数値変数を初期化します。これは、送信された読み取り値の数を追跡します。
unsigned int readingId = 0;
Wi-Fiチャンネルを変更する
次に、受信機の Wi-Fi チャネルを取得します。これは、同じ Wi-Fi チャネルを取得ボードに自動的に割り当てることができるため便利です。
これを行うには、次の行に SSID を挿入する必要があります。
constexpr char WIFI_SSID[] = "REPLACE_WITH_YOUR_SSID";
次に、getWiFiChannel() 関数がネットワークをスキャンし、そのチャネルを取得します。
int32_t getWiFiChannel(const char *ssid) {
if (int32_t n = WiFi.scanNetworks()) {
for (uint8_t i=0; i<n; i++) {
if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
return WiFi.channel(i);
}
}
}
return 0;
}
BME280センサーの初期化
このinitBME()関数は BME280 センサーを初期化します。
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
}
測定温度
readTemperature ()関数は、BME280 センサーの温度を読み取り、返します。
float readTemperature() {
float t = bme.readTemperature();
return t;
}
湿度の読み取り
readHumidity ()関数は、BME280 センサーから湿度を読み取り、返します。
float readHumidity() {
float h = bme.readHumidity();
return h;
}
OnDataSent コールバック関数
この OnDataSent() コールバック関数は、メッセージの送信時に実行されます。この場合、この関数はメッセージが正常に配信されたかどうかを出力します。
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
Serial.print("Last Packet Send Status: ");
if (sendStatus == 0){
Serial.println("Delivery success");
}
else{
Serial.println("Delivery fail");
}
}
セット ( )
シリアルモニターを初期化します。
Serial.begin(115200);
BME280 センサーを初期化します。
initBME();
ESP8266 を Wi-Fi ステーションとして設定します。
WiFi.mode(WIFI_STA);
受信機の Wi-Fi チャネルと一致するようにチャネルを設定します。
int32_t channel = getWiFiChannel(WIFI_SSID);
WiFi.printDiag(Serial); // Uncomment to verify channel number before
wifi_promiscuous_enable(1);
wifi_set_channel(channel);
wifi_promiscuous_enable(0);
WiFi.printDiag(Serial); // Uncomment to verify channel change after
ESP-NOWを初期化します。
// Init ESP-NOW
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
ESP8266 の役割を設定します。
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
ESP-NOW の初期化に成功したら、メッセージ送信時に呼び出されるコールバック関数を登録します。この場合、先ほど作成した OnDataSent() 関数を登録します。
esp_now_register_send_cb(OnDataSent);
ピアの追加
別のボード (受信機) にデータを送信するには、それをピアとしてペアにする必要があります。次のコードは、受信者を登録し、ピアとして追加します。
esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
ループ()
LOOP()内で、新しい測定値を取得および送信する時期が来たかどうかを確認します。
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// Save the last time a new reading was published
previousMillis = currentMillis;
ESP-NOW メッセージを送信する
最後に、メッセージ構造が ESP-NOW 経由で送信されます。
esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
コードを取得ボードにアップロードします。取得ボードの Wi-Fi チャネルが受信ボードの Wi-Fi チャネルに変更されることがわかります。
デモンストレーション
コードをすべての取得ボードにアップロードした後、すべてが期待どおりに進むと、ESP8266 受信ボードは他の取得ボードからセンサー データの受信を開始するはずです。
ローカル ネットワーク上でブラウザを開き、ESP8266 の IP アドレスを入力します。
各取得プレートの温度、湿度、および測定値が Web ページ上で更新された最終時刻をロードする必要があります。新しいパケットを受信すると、Web ページを更新しなくても、Web ページは自動的に更新されます。
要約する
このチュートリアルでは、ESP-NOW と Wi-Fi を使用して Web サーバーをセットアップし、ESP8266 NodeMCU ボードを使用して複数のボード (多対 1 構成) から ESP-NOW パケットを受信する方法を学習しました。
さらに、サーバー送信イベントを使用すると、新しいパケットを受信するたびに Web ページを更新することなく、Web ページを自動的に更新できます。