ESP32で開発する環境を整える。
開発環境のインストール
- Arduino IDE
- ボードマネージャ(「ファイル」→「環境設定」→「追加のボードマネージャーのURL」)
- ESP-WROOM-32
- https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
- esp32 > ESP32 Wrover Module or ESP32 Dev Module
- ESP-WROOM-02(ESP8266)
- http://arduino.esp8266.com/stable/package_esp8266com_index.json
- esp8266
- Generic ESP8266 Module
- ESP-WROOM-32
- モジュール
- matter(iOS『ホーム』)、esp32-alexa-wemo-emulator(Alexa)
- ボードマネージャ(「ファイル」→「環境設定」→「追加のボードマネージャーのURL」)
- Linuxの場合、usbserial.koとch341.koなどが必要。(/dev/ttyUSB0)
スケッチ
// Copyright © 2024 Yuichiro Nakada
#ifdef ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#define WebServer ESP8266WebServer
// https://qiita.com/daidai100/items/ce7f05f20498e5aa8873 (自作ESP32簡易ボード)
#else
#include <WiFi.h>
#include <WebServer.h>
#endif
/*Put your SSID & Password*/
const char* ssid = " YourNetworkName"; // Enter SSID here
const char* password = " YourPassword"; //Enter Password here
WebServer server(80);
#define MAXPINS 17
uint8_t pin[MAXPINS];
uint8_t status[MAXPINS] = {
0xff, // 0: Don't use!
0xff, // 1: No Digital Output
0xff, // 2: Don't use!
0xff, // 3: Nothing??
0x00, // 4
0x00, // 5
0xff, // 6: Nothing??
0xff, // 7: Nothing??
0xff, // 8: Nothing??
0xff, // 9: Nothing??
0xff, // 10: Nothing??
0xff, // 11: Nothing??
0x00, // 12
0x00, // 13
0x00, // 14
0xff, // 15: Don't use!
0x00, // 16
};
void setup()
{
Serial.begin(115200);
delay(100);
for (int i=0; i<MAXPINS; i++) {
pin[i] = i;
if (status[i]!=0xff) pinMode(pin[i], OUTPUT);
}
Serial.println("Connecting to ");
Serial.println(ssid);
// connect to your local wi-fi network
WiFi.begin(ssid, password);
// check wi-fi is connected to wi-fi network
int n = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
n++;
if (n>30) {
// server mode
Serial.println("");
byte mac[6];
WiFi.macAddress(mac);
String ssid = "";
for (int i=0; i<6; i++) {
ssid += String(mac[i], HEX);
}
Serial.println("SSID: " + ssid);
Serial.println("PASS: " + pass);
}
}
Serial.println("");
Serial.println("WiFi connected..!");
Serial.print("Got IP: "); Serial.println(WiFi.localIP());
server.on("/", handle_OnConnect);
server.on("/on", handle_on);
server.on("/off", handle_off);
server.onNotFound(handle_NotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop()
{
server.handleClient();
for (int i=0; i<MAXPINS; i++) {
if (status[i]==0xff) continue;
if (status[i]) digitalWrite(pin[i], HIGH);
else digitalWrite(pin[i], LOW);
}
}
void handle_OnConnect()
{
for (int i=0; i<MAXPINS; i++) {
if (status[i]!=0xff) status[i] = LOW;
}
server.send(200, "text/html", SendHTML());
}
void handle_on()
{
int n = server.arg("id").toInt();
status[n] = HIGH;
Serial.println("GPIO"+String(n)+" Status: ON");
server.send(200, "text/html", SendHTML());
}
void handle_off()
{
int n = server.arg("id").toInt();
status[n] = LOW;
Serial.println("GPIO"+String(n)+" Status: OFF");
server.send(200, "text/html", SendHTML());
}
void handle_NotFound()
{
server.send(404, "text/plain", "Not found");
}
String SendHTML()
{
String ptr = R"(<!DOCTYPE html> <html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>ESP32 Control</title>
<style>
html {font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}
body {margin-top: 50px;}
h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 50px;}
p {font-size: 14px;color: #888;margin-bottom: 10px;}
.button {display: block;width: 80px;background-color: #3498db;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}
.button-on {background-color: #3498db;}
.button-on:active {background-color: #2980b9;}
.button-off {background-color: #34495e;}
.button-off:active {background-color: #2c3e50;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
input:checked+.slider {background-color: #2196F3}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
/*.slider { -webkit-appearance: none; margin: 14px; width: 360px; height: 25px; background: #FFD65C; outline: none; -webkit-transition: .2s; transition: opacity .2s;}
.slider::-webkit-slider-thumb {-webkit-appearance: none; appearance: none; width: 35px; height: 35px; background: #003249; cursor: pointer;}
.slider::-moz-range-thumb { width: 35px; height: 35px; background: #003249; cursor: pointer; }*/
</style>
</head>
<body>
<h1>ESP32 Web Server</h1>
<h3>Using Station(STA) Mode</h3>
<!--%BUTTONPLACEHOLDER%-->
<script>
function toggleCheckbox(element) {
var xhr = new XMLHttpRequest();
if (element.checked){ xhr.open("GET", "/update?state=1", true); }
else { xhr.open("GET", "/update?state=0", true); }
xhr.send();
}
setInterval(function() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var inputChecked;
var outputStateM;
if (this.responseText == 1) {
inputChecked = true;
outputStateM = "On";
} else {
inputChecked = false;
outputStateM = "Off";
}
document.getElementById("output").checked = inputChecked;
document.getElementById("outputState").innerHTML = outputStateM;
}
};
xhttp.open("GET", "/state", true);
xhttp.send();
}, 1000);
</script>
)";
for (int i=0; i<MAXPINS; i++) {
if (status[i]==0xff) continue;
if (status[i]) {
ptr +="<p>GPIO" + String(i) + " Status: ON</p><a class=\"button button-off\" href=\"/off?id=" + String(i) + "\">OFF</a>\n";
} else {
ptr +="<p>GPIO" + String(i) + " Status: OFF</p><a class=\"button button-on\" href=\"/on?id=" + String(i) + "\">ON</a>\n";
}
}
ptr += "</body>\n";
ptr += "</html>\n";
return ptr;
}
- esp8266.ino (ESPAsyncWebServer)
// Copyright © 2024 Yuichiro Nakada
#ifdef ESP32
#include <WiFi.h>
#include <ESPmDNS.h>
//#include <WebServer.h>
#include <AsyncTCP.h>
#else // ESP8266
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
//#include <ESP8266WebServer.h>
//#define WebServer ESP8266WebServer
#include <ESPAsyncTCP.h>
// https://qiita.com/daidai100/items/ce7f05f20498e5aa8873 (自作ESP32簡易ボード)
#endif
// need to edit direct at ESPAsyncWebServer/src/WebResponseImpl.h
//#define TEMPLATE_PLACEHOLDER '$'
#include <ESPAsyncWebServer.h>
#define SSID "ssid" // Enter SSID here
#define PASSWORD "password" // Enter Password here
#define PASS "pass" // for server mode
#if __has_include("secret.h")
#include "secret.h"
#endif
AsyncWebServer server(80);
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ESP32 Control</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
<style>
/* https://pote-chil.com/css-stock/ja */
.heading-40 {
padding: .5em .7em;
border-top: 2px solid #5ba9f7;
border-bottom: 2px solid #5ba9f7;
background-image: linear-gradient(45deg, #5ba9f712 25%, transparent 25%, transparent 50%, #5ba9f712 50%, #5ba9f712 75%, transparent 75%, transparent), linear-gradient(-45deg, #5ba9f712 25%, transparent 25%, transparent 50%, #5ba9f712 50%, #5ba9f712 75%, transparent 75%, transparent);
background-color: #5ba9f70d;
background-size: 20px 20px;
color: #5ba9f7;
}
.toggle-button-4 {
display: flex;
align-items: center;
position: relative;
width: 100px;
height: 50px;
border-radius: 50px;
box-sizing: content-box;
background-color: #ff8d8d33;
cursor: pointer;
transition: background-color .4s;
}
.toggle-button-4:has(:checked) {
background-color: #75bbff33;
}
.toggle-button-4::before {
position: absolute;
left: 5px;
width: 42px;
height: 42px;
border-radius: 50%;
background-color: #ff8d8d;
content: '';
transition: left .4s;
}
.toggle-button-4:has(:checked)::before {
left: 50px;
background-color: #75bbff;
}
.toggle-button-4::after {
position: absolute;
left: 26px;
transform: translateX(-50%);
color: #fff;
font-weight: 600;
font-size: .9em;
content: 'OFF';
transition: left .4s;
}
.toggle-button-4:has(:checked)::after {
left: 71px;
content: 'ON';
}
.toggle-button-4 input {
display: none;
}
/* https://catnose.me/learning/html/input-range */
input[type="range"] {
-webkit-appearance: none;
appearance: none;
cursor: pointer; /* カーソルを分かりやすく */
outline: none; /* スライダーのアウトラインは見た目がキツイので消す */
height: 14px; /* バーの高さ */
width: 100%; /* バーの幅 */
background: #8acdff; /* バーの背景色 */
border-radius: 10px; /* バーの両端の丸み */
border: solid 3px #dff1ff; /* バー周囲の線 */
}
/* WebKit向けのつまみ */
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none; /* 🚩デフォルトのつまみのスタイルを解除 */
background: #53aeff; /* 背景色 */
width: 24px; /* 幅 */
height: 24px; /* 高さ */
border-radius: 50%; /* 円形に */
box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.15); /* 影 */
}
/* Moz向けのつまみ */
input[type="range"]::-moz-range-thumb {
background: #53aeff; /* 背景色 */
width: 24px; /* 幅 */
height: 24px; /* 高さ */
border-radius: 50%; /* 円形に */
box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.15); /* 影 */
border: none; /* デフォルトの線を消す */
}
/* Firefoxで点線が周りに表示されてしまう問題の解消 */
input[type="range"]::-moz-focus-outer {
border: 0;
}
/* つまみをドラッグしているときのスタイル */
input[type="range"]:active::-webkit-slider-thumb {
box-shadow: 0px 5px 10px -2px rgba(0, 0, 0, 0.3);
}
</style>
</head>
<body>
<section class="section">
<div class="container">
<h1 class="title">
ESP32 Web Server
</h1>
<p class="subtitle">
Using <strong>Station(STA)</strong> Mode!
</p>
$PLACEHOLDER$
</div>
</section>
<script>
function toggleCheckbox(element, id) {
var xhr = new XMLHttpRequest();
if (element.checked) { xhr.open("GET", "/update?id="+id+"&state=1", true); }
else { xhr.open("GET", "/update?id="+id+"&state=0", true); }
xhr.send();
}
setInterval(function() {
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
let array = this.responseText.split(",");
for (let i=0; i<array.length; i+=2) {
let inputChecked;
let outputStateM;
if (array[i+1] == 1) {
inputChecked = true;
outputStateM = "On";
} else {
inputChecked = false;
outputStateM = "Off";
}
document.getElementById("check"+array[i]).checked = inputChecked;
document.getElementById("outputState"+array[i]).innerHTML = outputStateM;
}
}
};
xhttp.open("GET", "/state", true);
xhttp.send();
}, 1000);
</script>
</body>
</html>
)rawliteral";
#define MAXPINS 17
uint8_t status[MAXPINS] = {
0xff, // 0: Don't use!
0xff, // 1: No Digital Output
0x00, // 2: LED
0xff, // 3: Nothing??
0x00, // 4
0x00, // 5
0xff, // 6: Nothing??
0xff, // 7: Nothing??
0xff, // 8: Nothing??
0xff, // 9: Nothing??
0xff, // 10: Nothing??
0xff, // 11: Nothing??
0x00, // 12
0x00, // 13
0x00, // 14
0xff, // 15: Don't use!
0x00, // 16
};
/*String getTemperature()
{
float t = dht.readTemperature();
if (isnan(t)) {
Serial.println("Failed to get temperature!");
return "--";
} else {
Serial.println(t);
return String(t) + " ℃";
}
}
String getHumidity()
{
float h = dht.readHumidity();
if (isnan(h)) {
Serial.println("Failed to get humidity!");
return "--";
} else {
Serial.println(h);
return String(h) + " %";
}
}*/
String outputState(int n)
{
// if (digitalRead(output)) return "checked";
if (status[n]) return " checked";
return "";
}
String processor(const String& var)
{
if (var=="PLACEHOLDER") {
String buttons = "";
for (int i=0; i<MAXPINS; i++) {
if (status[i]==0xff) continue;
buttons += "GPIO #" +String(i)+ "<div class='columns'><div class='column'><div class='select'><select><option>Digital Output</option><option>PWM</option><option>Digital Input</option><option>Analog Input</option></select></div></div><div class='column'><label class='toggle-button-4'><input type='checkbox' onchange=\"toggleCheckbox(this, " +String(i)+ ")\" id=\"check\"" +outputState(i)+ "></label></div></div>\n";
}
return buttons;
}
/*else if (var=="TEMPERATURE") {
return getTemperature();
} else if (var=="HUMIDITY") {
return getHumidity();
}*/
return "<"+var+">";
}
void loop()
{
}
void setup()
{
Serial.begin(115200);
delay(100);
for (int i=0; i<MAXPINS; i++) {
if (status[i]!=0xff) pinMode(i, OUTPUT);
}
Serial.println("Connecting to ");
Serial.println(SSID);
// connect to your local wi-fi network
WiFi.begin(SSID, PASSWORD);
// check wi-fi is connected to wi-fi network
int n = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
n++;
if (n>30) {
// server mode
Serial.println("");
byte mac[6];
WiFi.macAddress(mac);
String ssid = "";
for (int i=0; i<6; i++) {
ssid += String(mac[i], HEX);
}
Serial.println("SSID: " + ssid);
Serial.println("PASS: " PASS);
}
}
Serial.println("");
Serial.println("WiFi connected");
Serial.print("IP: "); Serial.println(WiFi.localIP());
// Activate mDNS this is used to be able to connect to the server
// with local DNS hostmane esp8266.local
if (MDNS.begin("esp8266")) {
Serial.println("MDNS responder started");
}
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(200, "text/html", index_html, processor);
});
/*server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(200, "text/plain", getTemperature().c_str());
});
server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send_P(200, "text/plain", getHumidity().c_str());
});*/
// Send a GET request to <ESP_IP>/update?state=<inputMessage>
server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
String inputMessage;
String inputParam;
int n = request->getParam("id")->value().toInt();
// GET input1 value on <ESP_IP>/update?state=<inputMessage>
if (request->hasParam("state")) {
inputMessage = request->getParam("state")->value();
inputParam = "state";
status[n] = inputMessage.toInt();
if (status[n]) digitalWrite(n, HIGH);
else digitalWrite(n, LOW);
} else {
inputMessage = "No message sent";
inputParam = "none";
}
Serial.println(inputParam +String(n)+ ":" + inputMessage);
request->send(200, "text/plain", "OK");
});
server.on("/state", HTTP_GET, [] (AsyncWebServerRequest *request) {
// request->send(200, "text/plain", String(digitalRead(output)).c_str());
request->send(200, "text/plain", ("5,"+String(status[5])).c_str());
});
server.begin();
Serial.println("HTTP server started");
}