気の向くままに辿るIT/ICT
webzoit.netウェブサイトホームページ
IoT・電子工作

ESP-01/12/32でブラウザ(スマホ/タブレット/PC)越しLED制御

ウェブ造ホーム前へ次へ
サイト内検索
カスタム検索
ESP8266って?

ESP-01/12/32でブラウザ(スマホ/タブレット/PC)越しLED制御

ESP-01/12/32でブラウザ(スマホ/タブレット/PC)越しLED制御

2018/07/07

 Wi-Fi(wifi)モジュールESP8266の内、ESP-01を使ってブラウザからLEDをON/OFFしてみるページ。

 動作確認に過ぎず、実用品ではないが、基本操作ということで。

 もちろん、パソコンだけでなく、スマホやタブレットからも操作可能(WiFiに接続後、ブラウザでIPアドレス指定)。

 前半では、プログラム中にHTMLを直書きするが、後半では、SPIFFSを使ってフラッシュメモリのファイルシステム上にHTMLファイルを置き、ブラウザからLEDを遠隔操作する。

 尚、ESP-01と題しているが、LED操作にしてもSPIFFSにしても、これでできるということは、上位機種のESP-02...、ESP-12...、ESP-WROOM-02、ESP32などESP-xxでもできるということで。

 ただし、検索してみるとSPIFFSにに関しては、ESP-01でもモノによっては、当初できていたものが、後にできなくなったという情報もあるようなので、その場合は、上位機種を使うとよいだろう。

 ちなみに自身が使用したESP-01は、直だった頃のAmazonマーケットプレイスHiLetgoから購入、2017/06/06に購入した(2017/06/17に届いた)もの。

前提

 Arduino IDEの[ツール] => [ボード]から[Generic ESP8266 Module]を選択、ESPにスケッチをアップロードできる状態であること。

 参考までに自身の使用しているOSは、Debian(Linux)、Arduino IDEのバージョンは、1.8.5。

ESP-01へのスケッチのアップロード準備

 ESP-01にプログラムをアップロードするには、USBポートを備えたCP2102やFTDI系のシリアルUSB変換モジュールを併用する必要がある。

 ESP-01の定格電圧は3.3VなのでシリアルUSB変換モジュールも3.3V専用か3.3V/5V兼用なら3.3Vに切り替えて接続する。

Rasbeeオリジナル FT232RL互換 3.3V/5V FTDI/USB/TTL変換アダプタ

 尚、ESP-01は、プログラム書き込み時と実行時でRSTとGPIO0のHIGH/LOWを巧みに切り替える必要があるが、RTS/DTRピン(ホール)のあるFTDIモジュールなら、これらにESP8266のRST/GPIO0をそれぞれ接続することで自動でアップロードできる為、これを使うことをおすすめする。

 ただ、これらRTS/DTR、ブレッドボード上でピンホールにジャンパワイヤを挿す場合、ピンヘッダや3.3V/5V切り替え用ジャンパピンのハンダ部が隆起しているのだが、ピンホールが、この付近にある為、USBシリアル変換モジュール並びに挿したジャンパワイヤを手でうまく押さえる必要はあるだろう。

 ちなみにESP-01については、SPIFFSを使おうにもフラッシュメモリ容量の関係でArduinoOTAは難しい模様。

ESP-01FTDI別電源
RXTX-
TXRX-
RSTRTS-
GPIO0DTR-
VCC-3.3V
GNDマイナス

 また、PCのUSBは最大500mAと大丈夫そうに思えるし、実際、たいていの場合、書き込みできるが、往々にしてWiFiモジュールは多くの電流を必要とすることがあり、ESPも最大800mAとなっている為、それらが要する電流量不足やパソコンのUSBポートの損傷回避などを考慮し、書き込み時も実行時も念の為、別電源をとった方がよいようだ。

 その際、ブレッドボード用電源モジュールと3Vよりも、4.5V以上の電池やACアダプタなどを利用すると良いだろう。

EasyWordMall 3.3V 5V MB102ブレッドボード用 電源モジュール パワーモジュール

KKHMF MB102ブレッドボード電源モジュール3.3V 5V Arduino Boardハンダ無しブレッドボード用

9V 電池ボックス DCケース5.5*2.1  スイッチ&背面カバーとプラグイン

uxcell バッテリコネクタ 5.5x2.1mm 9Vバッテリー用 接続バッテリー 4個入り

アルカリ乾電池 9V 1本 6LR61/B1P/V

SUCCUL ACアダプター 12V 1A センタープラス スイッチング式 最大出力12W 出力プラグ外径5.5mm(内径2.1mm)PSE取得品

スケッチ(HTML直書き)

/*********
  Rui Santos
  Complete project details at http://randomnerdtutorials.com  
*********/
 
// Load Wi-Fi library
#include <ESP8266WiFi.h>
 
// Replace with your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
 
// Replace any SSID/PW for ESP SoftAP Mode
#define SOFTAP_SSID "XXXXXX"
#define SOFTAP_PW "YYYYYY"
 
// Set web server port number to 80
WiFiServer server(80);
 
// Variable to store the HTTP request
String header;
 
// Auxiliar variables to store the current output state
String output2State = "off";
String output1State = "off";
 
// Assign output variables to GPIO pins
/*
const int output2 = 0;
const int output1 = 1;
*/
const int output2 = 2;
const int output1 = 3;
 
void setup() {
  Serial.begin(115200);
  // Initialize the output variables as outputs
  pinMode(output2, OUTPUT);
  pinMode(output1, OUTPUT);
  // Set outputs to LOW
  digitalWrite(output2, LOW);
  digitalWrite(output1, LOW);
 
  // AP+STAモードの設定
  WiFi.mode(WIFI_AP_STA);
  // APとして振る舞うためのSSIDとPW情報
  WiFi.softAP(SOFTAP_SSID, SOFTAP_PW);
  Serial.print("Connecting to ");
  Serial.println(SOFTAP_SSID);
  Serial.println("----------");
 
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
//    Serial.print(".");
    Serial.print(". ");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}
 
void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients
 
  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            // turns the GPIOs on and off
            if (header.indexOf("GET /5/on") >= 0) {
              Serial.println("GPIO 2 on");
              output2State = "on";
              digitalWrite(output2, HIGH);
            } else if (header.indexOf("GET /5/off") >= 0) {
              Serial.println("GPIO 2 off");
              output2State = "off";
              digitalWrite(output2, LOW);
            } else if (header.indexOf("GET /4/on") >= 0) {
              Serial.println("GPIO 1 on");
              output1State = "on";
              digitalWrite(output1, HIGH);
            } else if (header.indexOf("GET /4/off") >= 0) {
              Serial.println("GPIO 1 off");
              output1State = "off";
              digitalWrite(output1, LOW);
            }
            
            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the on/off buttons
            // Feel free to change the background-color and font-size attributes to fit your preferences
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #195B6A; border: none; color: white; padding: 16px 40px;");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button2 {background-color: #77878A;}</style></head>");
            
            // Web Page Heading
            client.println("<body><h1>ESP8266 Web Server</h1>");
            
            // Display current state, and ON/OFF buttons for GPIO 2  
            client.println("<p>GPIO 2 - State " + output2State + "</p>");
            // If the output2State is off, it displays the ON button      
            if (output2State=="off") {
              client.println("<p><a href=\"/5/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/5/off\"><button class=\"button button2\">OFF</button></a></p>");
            }
              
            // Display current state, and ON/OFF buttons for GPIO 1  
            client.println("<p>GPIO 1 - State " + output1State + "</p>");
            // If the output1State is off, it displays the ON button      
            if (output1State=="off") {
              client.println("<p><a href=\"/4/on\"><button class=\"button\">ON</button></a></p>");
            } else {
              client.println("<p><a href=\"/4/off\"><button class=\"button button2\">OFF</button></a></p>");
            }
            client.println("</body></html>");
            
            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}
 

 スケッチは、ESP8266 Web Server with Arduino IDEのものを使わせて頂いた。

 が、これ、STA/APモードにも関わらず、APモードがあまりに無防備だったため、太字部分(2ブロック)を追記。

 というわけでSTAモードでクライアントとして宅内無線LANアクセスポイントに、APモードとして宅内無線LANの同一ネットワークドメインにぶら下がる恰好で接続できることをnmapコマンドも併用して確認、ESP-01のアクセスポイントに無線カード内蔵PCを接続できることも確認した。

ピンの差し替え

 ブレッドボード上に抵抗220Ω程度-LED-GNDを2系統つなぎ、プラス側にスケッチでoutput1/output2に格納したピン番号に該当するピンを挿す。

ブラウザでアクセス

Connecting to SSID
. . . . . . .
WiFi connected.
IP address:
192.168.xxx.xxx
 

 PCが認識したUSBポートが、Arduino IDE上で選択されていることを確認、シリアルモニタを開き、baudrateは115200、改行なし(WindowsならCR+LFかも)とする。

 もし、何も表示されなければ、ESP-01の入力電源を入れ直すなどして再起動、宅内無線アクセスポイントに接続、表示されるはずのDHCPで払い出されたIPを確認する。

 ブラウザのURL入力欄に、ここで表示されたIPアドレスを入れてアクセスするとスケッチのgpio1/gpio2、それぞれにON/OFFを切り替えるボタンが1つずつ、計2つ表示される。

 出力ピンとして0〜3までの何れかを指定したのであれば、それらピンを正しくつないでいれば、何れもLEDが点灯・消灯するはずなのでボタンを押して確認する。

 尚、既にリモート操作できるようになっており、シリアルモニタでIPアドレスを確認するためだけにつないでおいたFTDIモジュールはなくてもLEDの点灯、消灯は切り替えられるのでPCからUSBを抜くなり、ESP-01間のジャンパワイヤを外すなりして当然だが、無線で操作できることを確認する。

ESP-01の出力ピン

 ちなみに先日、当てずっぽうで、ESP-01のGPIO0/GPIO2が出力ピンとなることを期待してpinModeの第1引数を0と1とした時、実際には、GPIO0/TXが出力ピンとして機能し、ブレッドボード上でLEDをつなげて確認してみたところ、TXピンの出力はオンボードの青いLEDと逆の挙動を示した。

 続いて以下のような遠回りをした結果、結局のところ、出力ピンとする場合(入力ピンとしては未確認)、特別なにか設定する必要もなく、普通にpinMode(pin,OUTPUT)としてpinに0/1/2/3を指定すれば、GPIO0/TX/GPIO2/RXの4ピンを出力ピンとして機能させることができることがわかった。

 esp8266_gpio_pin_allocationsによるとpinMode()宣言を使うとプログラム実行時には、TX、RXが、1、3として使えるように見える。

 また、そこにある表からするとpinMode()の第1引数に1(TX)、3(RX)、第2引数にFunction 0とするとTX/RX、Function 3とするとGPIO1/GPIO3になるかのように読み取れるも具体的な書き方は載っていない...。

 が、How to I make the tx and rx pins on an esp-8266-01 into GPIO pins?によれば、pinMode宣言の第2引数は、アンスコ付きのFUNCTION_0かFUNCTION_3で認識に間違いがなければ、TX/RXとGPIO1/GPIO3を同時に使うことはできないとある。

 試してみたところ、pinModeの第1引数を1と3とし、第2引数を共にFUNCTION_0/FUNCTION_3(としても0/3)として宣言してもエラーにはならず、書き込めることは書き込めるが、TX/RXピン何れからも出力を得ることはできなかった...。

 結局、徒労に終わり、前述のように1ピンと3ピンとして普通の出力ピンと同様に扱えばよいことがわかった。

 実は、当初、SPIFFS版のみ、記載するつもりでいたのだが、それ以前にAjaxだからなのか?自身にはよくわからないが、RX/TXをGPIOピンとして使うことができなかった為、急遽、RX/TXをGPIOピンとできたケースを前段に、SPIFFS版は、GPIO0/GPIO2のみGPIOピンとして使う方法と併せて後段に記載することにした次第。

ESP-01でSPIFFSを使いつつ、ブラウザからLEDをリモート操作

 ここまでは、プログラム中にHTMLを直書きしたが、次にHTMLファイルをESP-01のフラッシュメモリ内のファイルシステム上に保存しておく方法でブラウザ経由でLED操作をしてみる。

 これには、Spiffsを使う。

 Spiffsとは、比較的低速だが、少ない端子でマイコン内部で使われるデバイス同士を接続するバスであるSPI/Serial Peripheral Interfaceを使い、アクセスするNOR型フラッシュメモリ上にファイルシステムを配置できる、組み込み機器を想定した仕組み。

 言い換えると容量はさほど大きくないにしてもRAMにアクセスする必要もないフラッシュメモリ上にファイルを保存して、プログラム実行時には、そこから比較的高速に読み出せる(書き込みは、書き換え)という便利なもの。

 つまり、今回のケースでは、プログラムにHTMLを直書きしなくてもフラッシュメモリ上にHTMLファイルを置いておけるからプログラムがスッキリするよという話。

 ちなみにファイル単位というわけにはいかないものの、Arduinoにも、これと同じような仕組みとしてフラッシュメモリにデータを格納するためのPROGMEMキーワードがある...と思ったら、Guide to PROGMEM on ESP8266 and Arduino IDEによれば、PROGMEMは、ESP8266にも移植されているそうな。

ESP-01でSPIFFSを使うための準備

 ESP-01でSPIFFSを使うためには、ちょっとした準備が必要となる。

 Arduinoのlibrariesフォルダがあるフォルダ上(librariesフォルダと同じ階層)に[tools]フォルダを作成、その中にUpdate plugin for newer Arduino IDEにアクセスし、最新の.zip(執筆時点では、ESP8266FS-0.3.0.zip)をダウンロード・展開。

 既にArduino IDEを開いていた場合は、再起動、そうでなければ、起動するだけ。

 これで[ツール]メニューを見ると[ESP8266 Sketch Data Upload]という新たなメニューが表示されているはず。

 ただし、Arduino IDEのバージョンによっては、Stable版ではSPIFFSに未対応である為、開発版にする必要があるといった情報もあるので確認の上、そうするか、他のバージョンを使うとよいだろう。

Arduino IDEでSPIFFSを使うための準備

 Arduino IDEの[ツール] => [ボード]で[Generic ESP8266 Module]を選択した状態で[Flash Size:]を[512K (no SPIFFS)]以外のものを選ぶ。

 サイズを大きくすればするほど、かなり時間がかかる為、最小限のものを選ぶのが賢明。

 [Erase Flash:]は、それまでの作業を思い返しつつ、ボードへの書き込み状態によって適宜選択する。

SPIFFSにアップするデータの置き場

 フラッシュメモリにアップロードするデータは、パスの通ったArduino IDE用のスケッチごとのフォルダ下に作成した[data]フォルダ内に置く。

フラッシュメモリへのアップロード

 Arduino IDEで当該スケッチを開いた上で[ツール]メニューから先程追加されたはずの[ESP8266 Sketch Data Upload]を選択すると当該スケッチフォルダ下の[data]フォルダがフラッシュメモリ内のファイルシステム上に書き込まれる。

スケッチ(SPIFFS版)

 スケッチは、せっかくなので、Ajaxにしてみるのindex.htmlとinoをそのまま使わせて頂いた。

ブラウザでアクセス

 アクセス方法については、前段と同じ。

 尚、同スケッチのピン番号だけ0/1/2/3と変えて4通り試してみたところ、0(GPIO0)と2(GPIO2)はいけたものの、1(TX)と3(RX)は、点灯させることができなかったのだが、一体、なぜだろう...謎。

関連リンク