ESP8266とリレーモジュールでLED電球を制御する

リレーはコイルと、コイルに通電することで開閉する接点で構成されて、低入力電圧で高電圧デバイスを制御することができます。

リレーモジュールはリレー以外、LEDインジケータ、トランジスタ、還流ダイオードなどの電子部品も含まれて、制御が容易になります。

リレーモジュール pinout

次の図は 5V シングルチャンネルリレーモジュールです。

alt

出力側の端子台には COM (common), NC (normally closed), NO (normally open) の 3 つの端子があります。

  • COM 端子は共通端子。
  • NC(常閉)端子は常に COM 端子に接続する。
  • NO(常開)端子は常に開いているため、回路を閉じない限り COM に接続しない。

電球は長時間使用しないなので、NO 端子に接続します。

入力側は VCC、GND、IN の 3 つのピンがあります。 IN ピンは NodeMCU の GPIO ピンに接続し、リレーを制御します。active-low ピンなので、LOW にするとリレーが動作します。

NodeMCU V3 を使っているので、USB から給電する場合、5V リレーモジュールに電力を供給するには、VU (Vusb) ピンを使用します。リレーモジュールの VCC ピンを VU ピンに接続します。 ちなみに一部のリレーモジュールは 3.3V ピンに接続しても動作します。

以下は 2 チャンネルリレーモジュールを使用した例です。

alt

開発環境構築

非同期通信で電球の ON/OFF を制御したいため、ESPAsyncWebServer というライブラリを利用します。

私は PlatformIO IDE for VSCode を使っているので、platformio.inilib_deps パラメーターをこのライブラリ名に設定します。

[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
board_build.f_cpu = 80000000L
upload_speed = 115200
monitor_speed = 115200
platform_packages =
    platformio/framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git

lib_deps = ESP Async WebServer

コード

コードは以下となります。

PROGMEM を使って、フラッシュメモリに HTML コードを保存します。 注意すべき点は、rawliteral は 2 つの % 記号の間をプレースホルダーとして扱うので、 CSS コードでパーセントを指定する場合、%%% をエスケープする必要があります。 例えば translate(-50%, -50%)translate(-50%%, -50%%) になります。

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncWebServer.h>

const char *ssid = "SSID";
const char *password = "PASSWORD";
const char *PARAM_STATE = "state";
const int relayPin = 5;  // D1
boolean relayNO = true;  // whether the relay is normally open

AsyncWebServer server(80);

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Control LED</title>
    <style>
      .center { position: absolute; top: 50%%; left: 50%%; transform: translate(-50%%, -50%%); }
      .switch { position: relative; display: inline-block; width: 80px; height: 40px; }
      .switch input { display: none; }
      .slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #e0e0e0; border-radius: 40px; transition: 0.3s; }
      .slider::before { position: absolute; content: ""; height: 30px; width: 30px; top: 5px; left: 5px; background-color: #fff; border-radius: 50%; transition: 0.3s; }
      input:checked + .slider { background-color: #4caf50; }
      input:focus + .slider { box-shadow: 0 0 1px #4caf50; }
      input:checked + .slider:before { transform: translateX(40px); }
    </style>
  </head>
  <body>
    <div class="center">
      <h3 id="title">%LED_STATE%</h3>
      <label class="switch">
        <input type="checkbox" onchange="toggleSwitch(this)" %IS_CHECKED%/>
        <span class="slider"></span>
      </label>
    </div>
    <script>
      function toggleSwitch(e) {
        const req = new XMLHttpRequest();
        const title = document.getElementById("title");
        if (e.checked) { req.open("GET", "/light?state=1", true); title.innerHTML =  "LED Lamp ON"; }
        else { req.open("GET", "/light?state=0", true); title.innerHTML =  "LED Lamp OFF"; }
        req.send();
      }
    </script>
  </body>
</html>
)rawliteral";

String processor(const String &var) {
    if (var == "LED_STATE") {
        String stateVal;
        if (relayNO) {
            stateVal = (digitalRead(relayPin)) ? "OFF" : "ON";
        } else {
            stateVal = (digitalRead(relayPin)) ? "ON" : "OFF";
        }
        String stateText = "LED Lamp " + stateVal;
        return stateText;
    } else if (var == "IS_CHECKED") {
        String checkbox;
        if (relayNO) {
            checkbox = (digitalRead(relayPin)) ? "" : "checked";
        } else {
            checkbox = (digitalRead(relayPin)) ? "checked" : "";
        }
        return checkbox;
    }

    return String();
}

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.printf("WiFi Failed!\n");
        return;
    }

    Serial.println("");
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("IP Address: ");
    Serial.println(WiFi.localIP());

    pinMode(relayPin, OUTPUT);
    if (relayNO) {
        digitalWrite(relayPin, HIGH);
    } else {
        digitalWrite(relayPin, LOW);
    }

    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
        request->send_P(200, "text/html", index_html, processor);
    });

    server.on("/light", HTTP_GET, [](AsyncWebServerRequest *request) {
        String state;
        if (request->hasParam(PARAM_STATE)) {
            state = request->getParam(PARAM_STATE)->value();
            if (relayNO) {
                digitalWrite(relayPin, !state.toInt());
            } else {
                digitalWrite(relayPin, state.toInt());
            }
        } else {
            state = "No state sent";
        }
        request->send(200, "text/plain", "[GET]: " + state);
    });

    server.begin();
}

void loop() {}

IP アドレスはシリアルモニターで出力されます。

ブラウザからこの IP アドレスにアクセスすると、トグルスイッチが画面に表示されます。このスイッチの ON/OFF で LED 電球を点滅できます。