Dominik
Published © GPL3+

Host Web Page Over the Internet on ESP32 Using SD Card

ESP32 + HTTP server + WebSockets + Bootstrap + Husarnet + configuration and HTML files on SD card.

EasyFull instructions provided1 hour9,962
Host Web Page Over the Internet on ESP32 Using SD Card

Things used in this project

Hardware components

ESP32S
Espressif ESP32S
×1
MicroSD Card with Adapter
Digilent MicroSD Card with Adapter
×1
Memory Socket, SD Card
Memory Socket, SD Card
×1

Software apps and online services

Husarnet client
Husarnet client
Arduino IDE
Arduino IDE

Story

Read more

Code

ESP32-web-template-sd.ino

Arduino
#include <WiFi.h>
#include <WiFiMulti.h>

#include <WebSocketsServer.h>
#include <Husarnet.h>
#include <ArduinoJson.h>

// Libraries for SD card
#include "FS.h"
#include "SD.h"
#include <SPI.h>

#define HTTP_PORT 8000
#define WEBSOCKET_PORT 8001

const int BUTTON_PIN = 10;
const int LED_PIN = 11;

const int SDCS_PIN = 22;

typedef struct {
  char ssid[30];
  char pass[30];
} WifiNetwork;

struct WifiConfig {
  WifiNetwork net[10];
};

struct HusarnetConfig {
  char hostname[30];
  char joincode[30];
};

WifiConfig wifi_conf;
HusarnetConfig husarnet_conf;

int wifi_net_no = 0;

WiFiMulti wifiMulti;

WebSocketsServer webSocket = WebSocketsServer(WEBSOCKET_PORT);
HusarnetServer server(HTTP_PORT);

//Use https://arduinojson.org/v5/assistant/ to compute buffer size
StaticJsonBuffer<200> jsonBufferTx;
StaticJsonBuffer<100> jsonBufferRx;
StaticJsonBuffer<500> jsonBufferSettings;

JsonObject& rootTx = jsonBufferTx.createObject();

char* html;
bool wsconnected = false;

void onWebSocketEvent(uint8_t num, WStype_t type, uint8_t* payload, size_t length) {
  switch (type) {
    case WStype_DISCONNECTED:
      {
        wsconnected = false;
        Serial.printf("ws [%u] Disconnected\r\n", num);
      }
      break;
    case WStype_CONNECTED:
      {
        wsconnected = true;
        Serial.printf("ws [%u] Connection from Husarnet\r\n", num);
      }
      break;

    case WStype_TEXT:
      {
        Serial.printf("ws [%u] Text:\r\n", num);
        for (int i = 0; i < length; i++) {
          Serial.printf("%c", (char)(*(payload + i)));
        }
        Serial.println();

        JsonObject& rootRx = jsonBufferRx.parseObject(payload);
        jsonBufferRx.clear();

        uint8_t ledState = rootRx["led"];

        Serial.printf("LED state = %d\r\n", ledState);
        if (ledState == 1) {
          digitalWrite(LED_PIN, HIGH);
        }
        if (ledState == 0) {
          digitalWrite(LED_PIN, LOW);
        }
      }
      break;

    case WStype_BIN:
    case WStype_ERROR:
    case WStype_FRAGMENT_TEXT_START:
    case WStype_FRAGMENT_BIN_START:
    case WStype_FRAGMENT:
    case WStype_FRAGMENT_FIN:
    default:
      Serial.printf("ws [%u] other event\r\n", num);
      break;
  }

}

void writeFile(fs::FS &fs, const char * path, const char * message);

void taskWifi( void * parameter );
void taskHTTP( void * parameter );
void taskWebSocket( void * parameter );
void taskStatus( void * parameter );

void setup()
{
  Serial.begin(115200);

  pinMode(BUTTON_PIN, INPUT_PULLUP);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  // init SD card
  SD.begin(SDCS_PIN);
  if (!SD.begin(SDCS_PIN)) {
    Serial.println("Attach SD card and restart");
    return;
  }

  uint8_t cardType = SD.cardType();
  switch (cardType) {
    case CARD_NONE:
    case CARD_UNKNOWN:
      Serial.println("Attach SD card and restart");
      return;
    case CARD_SD:
      Serial.println("SD card detected");
      break;
    case CARD_SDHC:
      Serial.println("SDHC card detected");
      break;
  }

  //read html file from SD card
  File file = SD.open("/index.htm");
  if (!file) {
    Serial.println("index.htm file doesn't exist. Save it on SD card and restart.");
    return;
  }
  Serial.printf("index.htm file size: %d\r\n", file.size());
  html = new char [file.size() + 1];
  file.read((uint8_t*)html, file.size());
  html[file.size()] = 0;  //make a CString
  file.close();

  //read network credentials from SD card
  file = SD.open("/settings.js");
  if (!file) {
    Serial.println("settings.js file doesn't exist. Save it on SD card and restart.");
    return;
  }
  Serial.printf("settings.js file size: %d\r\n", file.size());
  char* setting_json = new char [file.size() + 1];
  file.read((uint8_t*)setting_json, file.size());
  setting_json[file.size()] = 0;  //make a CString
  file.close();

  JsonObject& rootSettings = jsonBufferSettings.parseObject(setting_json);
  String husarnet_hostname = rootSettings["husarnet"]["hostname"];
  String husarnet_joincode = rootSettings["husarnet"]["joincode"];
  strcpy (husarnet_conf.hostname, husarnet_hostname.c_str());
  strcpy (husarnet_conf.joincode, husarnet_joincode.c_str());
  Serial.println();
  Serial.println("husarnet");
  Serial.printf("hostname: %s\r\n", husarnet_conf.hostname);
  Serial.printf("joincode: %s\r\n", husarnet_conf.joincode);

  JsonArray& wifinetworks = rootSettings["wifi"];
  wifi_net_no = wifinetworks.size();
  Serial.println();
  Serial.printf("number of Wi-Fi networks: %d\r\n", wifi_net_no);

  if (wifi_net_no > 10) {
    Serial.println("too many WiFi networks on SD card, 10 max");
    wifi_net_no = 10;
  }

  for (int i = 0; i < wifi_net_no; i++) {
    String ssid = wifinetworks[i]["ssid"];
    String pass = wifinetworks[i]["pass"];
    wifiMulti.addAP(ssid.c_str(), pass.c_str());

    Serial.printf("WiFi %d: SSID: \"%s\" ; PASS: \"%s\"\r\n", i, ssid.c_str(), pass.c_str());
  }

  xTaskCreate(
    taskWifi,          /* Task function. */
    "taskWifi",        /* String with name of task. */
    10000,            /* Stack size in bytes. */
    NULL,             /* Parameter passed as input of the task */
    1,                /* Priority of the task. */
    NULL);            /* Task handle. */

  xTaskCreate(
    taskHTTP,          /* Task function. */
    "taskHTTP",        /* String with name of task. */
    10000,            /* Stack size in bytes. */
    NULL,             /* Parameter passed as input of the task */
    2,                /* Priority of the task. */
    NULL);            /* Task handle. */

  xTaskCreate(
    taskWebSocket,          /* Task function. */
    "taskWebSocket",        /* String with name of task. */
    10000,            /* Stack size in bytes. */
    NULL,             /* Parameter passed as input of the task */
    1,                /* Priority of the task. */
    NULL);            /* Task handle. */

  xTaskCreate(
    taskStatus,          /* Task function. */
    "taskStatus",        /* String with name of task. */
    10000,            /* Stack size in bytes. */
    NULL,             /* Parameter passed as input of the task */
    1,                /* Priority of the task. */
    NULL);            /* Task handle. */
}

void taskWifi( void * parameter ) {
  while (1) {
    uint8_t stat = wifiMulti.run();
    if (stat == WL_CONNECTED) {
      Serial.println("");
      Serial.println("WiFi connected");
      Serial.println("IP address: ");
      Serial.println(WiFi.localIP());

      Husarnet.join(husarnet_conf.joincode, husarnet_conf.hostname);
      Husarnet.start();

      server.begin();

      while (WiFi.status() == WL_CONNECTED) {
        delay(500);
      }
    } else {
      Serial.printf("WiFi error: %d\r\n", (int)stat);
      delay(500);
    }
  }
}

void taskHTTP( void * parameter )
{
  String header;

  while (1) {
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
    }

    HusarnetClient client = server.available();

    if (client) {
      Serial.println("New Client.");
      String currentLine = "";
      Serial.printf("connected: %d\r\n", client.connected());
      while (client.connected()) {

        if (client.available()) {
          char c = client.read();
          Serial.write(c);
          header += c;
          if (c == '\n') {
            if (currentLine.length() == 0) {
              client.println("HTTP/1.1 200 OK");
              client.println("Content-type:text/html");
              client.println("Connection: close");
              client.println();

              client.println(html);
              break;
            } else {
              currentLine = "";
            }
          } else if (c != '\r') {
            currentLine += c;
          }
        }
      }

      header = "";

      client.stop();
      Serial.println("Client disconnected.");
      Serial.println("");
    } else {
      delay(200);
    }
  }
}

void taskWebSocket( void * parameter )
{
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  webSocket.begin();
  webSocket.onEvent(onWebSocketEvent);

  while (1) {
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
    }
    webSocket.loop();
    delay(1);
  }
}

void taskStatus( void * parameter )
{
  String output;
  int cnt = 0;
  uint8_t button_status = 0;
  while (1) {
    if (wsconnected == true) {
      if (digitalRead(BUTTON_PIN) == LOW) {
        button_status = 1;
      } else {
        button_status = 0;
      }
      output = "";

      rootTx["counter"] = cnt++;
      rootTx["button"] = button_status;
      rootTx.printTo(output);

      Serial.print(F("Sending: "));
      Serial.print(output);
      Serial.println();

      webSocket.sendTXT(0, output);
    }
    delay(100);
  }
}

void loop()
{
  delay(5000);
}

index.htm

HTML
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <link
      rel="stylesheet"
      href="https://use.fontawesome.com/releases/v5.0.13/css/all.css"
      integrity="sha384-DNOHZ68U8hZfKXOrtjWvjxusGo9WQnrNx2sqG0tfsghAvtVlRW3tvkXWZh58N9jp"
      crossorigin="anonymous"
    />
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
      integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB"
      crossorigin="anonymous"
    />
    <title>ESP32 + Bootstrap + WebSocket + JSON + Husarnet</title>

    <script type="text/javascript">
      var ws;

      function mouseDown() {
        ws.send('{"led" : 1}');
      }

      function mouseUp() {
        ws.send('{"led" : 0}');
      }

      function WebSocketBegin() {
        if ("WebSocket" in window) {
          // Let us open a web socket
			ws = new WebSocket(
				location.hostname.match(/\.husarnetusers\.com$/) ? "wss://" + location.hostname + "/__port_8001/" : "ws://" + location.hostname + ":8001/"
            );
          //ws = new WebSocket(
          //  "wss://fc94f91f5992989f83474cc8abf7329b-8001.husarnetusers.com"
          //);

          ws.onopen = function() {
            // Web Socket is connected
          };

          ws.onmessage = function(evt) {
            //create a JSON object
            var jsonObject = JSON.parse(evt.data);
            var cnt = jsonObject.counter;
            var btn = jsonObject.button;

            document.getElementById("cnt").innerText = cnt;
            if (btn == 1) {
              document.getElementById("btn").style.color = "green";
            } else {
              document.getElementById("btn").style.color = "red";
            }
          };

          ws.onclose = function() {
            // websocket is closed.
            alert("Connection is closed...");
          };
        } else {
          // The browser doesn't support WebSocket
          alert("WebSocket NOT supported by your Browser!");
        }
      }
    </script>
  </head>

  <body onLoad="javascript:WebSocketBegin()">
    <header id="main-header" class="py-2 bg-success text-white">
      <div class="container">
        <div class="row justify-content-md-center">
          <div class="col-md-6 text-center">
            <h1><i class="fas fa-cog"></i> ESP32 control</h1>
          </div>
        </div>
      </div>
    </header>

    <section class="py-5 bg-white">
      <div class="container">
        <div class="row">
          <div class="col">
            <div class="card bg-light m-2" style="min-height: 15rem;">
              <div class="card-header">Input 1</div>
              <div class="card-body">
                <h5 class="card-title">Counter</h5>
                <p class="card-text">
                  A counter value is updated every 100ms by ESP32.
                </p>
                <p id="cnt" class="font-weight-bold">
                  0
                </p>
              </div>
            </div>
          </div>
          <div class="col">
            <div class="card bg-light m-2" style="min-height: 15rem;">
              <div class="card-header">Input 2</div>
              <div class="card-body">
                <h5 class="card-title">Button</h5>
                <p class="card-text">
                  Press the push button on ESP32 board to change a color of the
                  dot below.
                </p>
                <i id="btn" class="fas fa-circle fa-2x" style="color:red;"></i>
              </div>
            </div>
          </div>
        </div>
        <div class="row">
          <div class="col">
            <div class="card bg-light m-2">
              <div class="card-header">Output</div>
              <div class="card-body">
                <h5 class="card-title">LED</h5>
                <p class="card-text">
                  Press the button to turn LED on. Release to turn LED off.
                </p>
                <button
                  type="button"
                  class="btn btn-lg btn-warning btn-block"
                  onmousedown="mouseDown()"
                  onmouseup="mouseUp()"
                  ontouchstart="mouseDown()"
                  ontouchend="mouseUp()"
                >
                  click
                </button>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  </body>
</html>

settings.js

JSON
{
	"husarnet":{
		"hostname":"esp32template",	
		"joincode":"fc94:b01d:1803:8dd8:b293:5c7d:7639:932a/xxxxxxxxxxxxxxxxxxxxxx"
	},
	"wifi":[
		{
			"ssid":"mywifinet123",
			"pass":"qwerty"
		},
		{
			"ssid":"officenet",
			"pass":"admin1"
		},
		{
			"ssid":"iPhone(Johny)",
			"pass":"12345"
		}
	]
}

ESP32-web-template-sd

Credits

Dominik

Dominik

9 projects • 31 followers

Comments

Add projectSign up / Login