esp01s-serial-remote-controller

Serial communication from ESP-01S served webpage
Index Commits Files Refs README LICENSE
commit ee56cfa4320489244a526b60607950cbaa607f05
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date:   Thu,  9 Apr 2026 11:41:58 -0300

Add files

Diffstat:
AREADME.md | 18++++++++++++++++++
Adata/index.html | 43+++++++++++++++++++++++++++++++++++++++++++
Adata/main.js | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adata/style.css | 41+++++++++++++++++++++++++++++++++++++++++
Aplatformio.ini | 19+++++++++++++++++++
Asrc/main.cpp | 250+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 511 insertions(+), 0 deletions(-)
diff --git a/README.md b/README.md
@@ -0,0 +1,18 @@
+```console
+$ esptool -p /dev/ttyACM0 flash-id
+Connected to ESP8266 on /dev/ttyACM0:
+Chip type:          ESP8266EX
+Features:           Wi-Fi, 160MHz
+Crystal frequency:  26MHz
+MAC:                ec:64:c9:e7:bb:f8
+
+Stub flasher running.
+
+Flash Memory Information:
+=========================
+Manufacturer: 68
+Device: 4016
+Detected flash size: 4MB
+
+Hard resetting via RTS pin...
+```
diff --git a/data/index.html b/data/index.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>ESP8266 Remote Panel</title>
+        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+        <link rel="icon" href="data:,">
+        <link rel="stylesheet" type="text/css" href="style.css">
+        <script type="text/javascript" src="main.js"></script>
+    </head>
+    <body>
+        <div id="main-div">
+            <h1 id="title">ESP8266 Remote Panel</h1>
+            <div id="info-div">
+                <table style="width: 100%; padding: 1em;">
+                    <tr>
+                        <td>
+                            WiFi SSID
+                        </td>
+                        <td>
+                            <div id="wifi-ssid"></div>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            WiFi Strength
+                        </td>
+                        <td>
+                            <div id="wifi-rssi"></div>
+                        </td>
+                    </tr>
+                    <tr>
+                        <td>
+                            Local IP address
+                        </td>
+                        <td>
+                            <a id="system-ip-addr"></a>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+    </body>
+</html>
diff --git a/data/main.js b/data/main.js
@@ -0,0 +1,140 @@
+var received_data;
+var time_div;
+var system_time, ip_addr, wifi_ssid, wifi_rssi;
+
+const time_formatter = new Intl.DateTimeFormat('en-GB', {
+    timeZone: 'America/Argentina/Buenos_Aires',
+    year: 'numeric',
+    month: '2-digit',
+    day: '2-digit',
+    hour: '2-digit',
+    minute: '2-digit',
+    hour12: false
+});
+
+const query_types = {
+    "QUERY_STATUS": 0,
+}
+
+function rssi_to_percentage(rssi) {
+    var quality = 0; 
+
+    if (rssi <= -100) { 
+        quality = 0; 
+    } else if (rssi >= -50) { 
+        quality = 100; 
+    } else { 
+        quality = 2 * (rssi + 100); 
+    } 
+    return quality; 
+}
+
+/*
+function update_checkbox() {
+    main_checkbox.setAttribute("checked", received_data["main-output-enabled"]);
+    timer_checkbox.setAttribute("checked", received_data["timer"]["enabled"]);
+}
+
+function update_time() {
+    system_time = received_data["system-time"];
+    system_time_elem.innerHTML = time_formatter.format(new Date(system_time*1000))
+        .replaceAll("/", "-")
+        .replaceAll(",", "") + " GMT-3";
+}
+
+function update_timer() {
+    from_time.value = received_data["timer"]["from"]["hour"] + ":"
+        + received_data["timer"]["from"]["minute"];
+    to_time.value = received_data["timer"]["to"]["hour"] + ":"
+        + received_data["timer"]["to"]["minute"];
+}
+*/
+
+function update_all() {
+    /*
+    update_checkbox();
+    update_time();
+    update_timer();
+
+    system_local_domain.href = "http://" + received_data["system-local-domain"] + ".local";
+    system_local_domain.innerHTML = "http://" + received_data["system-local-domain"] + ".local";
+    */
+
+    system_ip_addr.href = "/"
+    system_ip_addr.innerHTML = received_data["system-ip-addr"];
+
+    /*
+    last_ntp_sync = received_data["last-ntp-sync"];
+    last_ntp_sync_elem.innerHTML = time_formatter.format(new Date(last_ntp_sync*1000))
+        .replaceAll("/", "-")
+        .replaceAll(",", "") + " GMT-3";
+    */
+
+    wifi_ssid.innerHTML = received_data["wifi-ssid"];
+    const rssi = received_data["wifi-rssi"];
+    wifi_rssi.innerHTML = rssi + "dBm (" + rssi_to_percentage(rssi) + "%)";
+}
+
+function socket_onmessage_handler(event) {
+    received_data = JSON.parse(event.data);
+
+    switch(received_data["type"]) {
+        case "all":
+            update_all();
+            break;
+        default:
+            console.log("[SOCKET] received_data: type not known, updating all...");
+            update_all();
+    }
+}
+
+function socket_onopen_handler(event) {
+    console.log("[SOCKET] Connection established")
+    socket.send(query_types["QUERY_STATUS"]);
+}
+
+/*
+function handle_click(cb) {
+    if(cb == 'main-toggle') {
+        socket.send(query_types["MAIN_OUTPUT_TOGGLE"]);
+    } else if(cb == 'timer-toggle') {
+        socket.send(query_types["TIMER_TOGGLE"]);
+    } else {
+        console.log("handle_click: wrong target");
+    }
+}
+*/
+
+/*
+function update_system_time() {
+    socket.send(query_types["MAIN_OUTPUT_STATUS"]);
+}
+
+function timer_set_time() {
+    var query_json = new Object();
+
+    query_json.from = new Object();
+    query_json.from.hour = from_time.value.slice(0, 2);
+    query_json.from.minute = from_time.value.slice(3, 5);
+
+    query_json.to = new Object();
+    query_json.to.hour = to_time.value.slice(0, 2);
+    query_json.to.minute = to_time.value.slice(3, 5);
+
+    socket.send(query_types["TIMER_SET_VALUES"] + JSON.stringify(query_json));
+}
+*/
+
+function query_data() {
+    system_time_elem = document.getElementById("system-time");
+    system_ip_addr = document.getElementById("system-ip-addr");
+    wifi_ssid = document.getElementById("wifi-ssid");
+    wifi_rssi = document.getElementById("wifi-rssi");
+
+    // setInterval(update_system_time, 30*1000); // update time every 30 secs
+}
+
+const socket = new WebSocket("ws:/" + "/" + location.host + ":81");
+socket.onmessage = socket_onmessage_handler;
+socket.onopen = socket_onopen_handler;
+window.onload = query_data;
diff --git a/data/style.css b/data/style.css
@@ -0,0 +1,41 @@
+body {
+    margin: 3em auto 3em auto;
+    /* font-family: "Nimbus Sans", "Nimbus Sans L", "Helvetica", Inter; */
+    font-family: monospace;
+    font-size: 18px;
+}
+
+#main-div {
+    max-width: 640px;
+    text-align: center;
+    margin-left: auto;
+    margin-right: auto;
+    padding: 12px;
+}
+
+#info-div {
+    border: 1px solid black;
+    border-radius: 12px;
+
+    td:nth-child(1) {
+        text-align: left;
+        user-select: none;
+    }
+    td:nth-child(2) {
+        text-align: right;
+        font-family: monospace;
+    }
+}
+
+@media (prefers-color-scheme: dark) {
+    body {
+        color: white;
+        background-color: #17171A;
+    }
+    a {
+        color: skyblue;
+    }
+    #info-div {
+        border-color: grey;
+    }
+}
diff --git a/platformio.ini b/platformio.ini
@@ -0,0 +1,19 @@
+[env:nodemcuv2]
+platform = espressif8266
+framework = arduino
+board = esp01
+upload_protocol = esptool
+lib_deps =
+    links2004/WebSockets
+    arduino-libraries/Arduino_JSON
+
+; Set CPU to 160MHz
+board_build.f_cpu = 160000000L
+
+; 4MB flash size and reserve 1MB for filesystem
+board_build.flash_size = 4MB
+board_build.ldscript = eagle.flash.4m1m.ld
+; board_build.ldscript = eagle.flash.4m512.ld ; 3.5MB Code 512KB Filesystem
+
+; Set filesystem to LittleFS
+board_build.filesystem = littlefs
diff --git a/src/main.cpp b/src/main.cpp
@@ -0,0 +1,250 @@
+#include <Arduino.h>
+#include <Arduino_JSON.h>
+#include <ESP8266WiFi.h>
+#include <WebSocketsServer.h>
+#include <ESP8266WebServer.h>
+#include <LittleFS.h>
+
+#ifndef LED_BUILTIN
+#define LED_BUILTIN    2
+#endif
+
+#define REMOTE_LED_PIN 2
+#define WIFI_SSID      "abcdefgh"
+#define WIFI_PASSWD    "12345678"
+#define LOG_ENABLE     true
+
+#if true == LOG_ENABLE
+#define LOG(fmt, ...) do {             \
+        Serial.printf(fmt, ##__VA_ARGS__); \
+        Serial.flush();                    \
+    } while (0)
+#else
+#define LOG(fmt, ...)
+#endif
+
+#define LOG_INFO(fmt, ...) LOG("[INFO] " fmt, ##__VA_ARGS__)
+#define LOG_ERROR(fmt, ...) LOG("[ERROR] " fmt, ##__VA_ARGS__)
+
+typedef enum {
+    QUERY_STATUS
+} query_t;
+
+ESP8266WebServer server(80);
+WebSocketsServer web_socket = WebSocketsServer(81);
+static uint8_t remote_devices;
+JSONVar data;
+
+void setup_wifi();
+void setup_websocket();
+void setup_webserver();
+void setup_fs();
+
+void websocket_event_handler(uint8_t num, WStype_t type, uint8_t *payload, size_t len);
+int webserver_get_file(String path, String &return_page);
+String webserver_file_content_type(String path);
+void webserver_file_handler();
+void webserver_handle_root();
+
+void update_data();
+
+void setup_wifi()
+{
+    WiFi.begin(WIFI_SSID, WIFI_PASSWD);
+
+    LOG_INFO("Connecting to WiFi");
+
+    while (WiFi.status() != WL_CONNECTED)
+    {
+        delay(200);
+        LOG(".");
+    }
+
+    LOG_INFO("Connected to '%s', IP address %d.%d.%d.%d\n", WIFI_SSID,
+             WiFi.localIP()[0],
+             WiFi.localIP()[1],
+             WiFi.localIP()[2],
+             WiFi.localIP()[3]);
+}
+
+void setup_websocket()
+{
+    web_socket.begin();
+    web_socket.onEvent(websocket_event_handler);
+}
+
+void setup_webserver()
+{
+    LOG_INFO("Loading server response from file 'index.html'\n");
+
+    server.on("/", webserver_handle_root);
+    server.onNotFound(webserver_file_handler);
+
+    server.begin();
+}
+
+void setup_fs()
+{
+    if (LittleFS.begin() == 0)
+    {
+        LOG_ERROR("Error couldn't begin filesystem!\n");
+    }
+
+    FSInfo fs_info;
+    LittleFS.info(fs_info);
+    LOG_INFO("LittleFS started %d bytes used\n", fs_info.usedBytes);
+}
+
+void websocket_event_handler(uint8_t num, WStype_t type, uint8_t *payload, size_t len)
+{
+    String data_as_json;
+    IPAddress ip;
+    uint8_t query_type;
+
+    switch(type)
+    {
+        case WStype_DISCONNECTED:
+
+            LOG_INFO("%d: Disconnected\n", num);
+            remote_devices -= 1;
+            digitalWrite(REMOTE_LED_PIN, remote_devices ? LOW : HIGH);
+
+        break;
+
+        case WStype_CONNECTED:
+
+            ip = web_socket.remoteIP(num);
+            LOG_INFO("%u: Connected from %d.%d.%d.%d, URL '%s'\n",
+                    num, ip[0], ip[1], ip[2], ip[3], payload);
+            remote_devices += 1;
+            digitalWrite(REMOTE_LED_PIN, remote_devices ? LOW : HIGH);
+
+        break;
+
+        case WStype_TEXT:
+
+            query_type = *payload - '0';
+            switch(query_type)
+            {
+                case QUERY_STATUS:
+                    update_data();
+                    data["type"] = "all";
+                    data_as_json = JSON.stringify(data);
+                    web_socket.sendTXT(num, data_as_json);
+                break;
+
+                default:
+                    LOG_INFO(" %s\n", payload);
+                break;
+            }
+
+            break;
+
+        case WStype_BIN:
+
+            LOG_INFO("%u: get binary length: %u\n", num, len);
+            hexdump(payload, len);
+
+        break;
+
+        case WStype_ERROR:
+        default: break;
+    }
+}
+
+int webserver_get_file(String path, String &return_page)
+{
+    if (LittleFS.exists(path))
+    {
+        LOG_INFO("Serving file '%s'\n", path.c_str());
+        File file = LittleFS.open(path.c_str(), "r");
+        while (file.available())
+        {
+            return_page += (char)file.read();
+        }
+        file.close();
+    }
+    else
+    {
+        LOG_INFO("'%s' File Not Found\n", path.c_str());
+        return_page = R"==(<!DOCTYPE html>
+        <html>
+          <head>
+              <title>ERROR 404: File Not found!!</title>
+              <meta name="viewport" content="width=device-width, initial-scale=1.0">
+          </head>
+          <body>
+              <h>ERROR 404: File Not Found!</h1>
+              <p>file '/index.html' not found</p>
+          </body>
+        </html>)==";
+        return 1;
+    }
+    return 0;
+}
+
+String webserver_file_content_type(String path)
+{
+    if (path.endsWith(".html")) return "text/html";
+    else if (path.endsWith(".css")) return "text/css";
+    else if (path.endsWith(".js")) return "application/javascript";
+    else if (path.endsWith(".ico")) return "image/x-icon";
+    else if (path.endsWith(".gz")) return "application/x-gzip";
+    return "text/plain";
+}
+
+void webserver_file_handler()
+{
+    String path = server.uri();
+    String requested_page;
+    int response_code;
+    response_code = webserver_get_file(path, requested_page) ? 404 : 200;
+    server.send(response_code, webserver_file_content_type(path), requested_page);
+}
+
+void webserver_handle_root()
+{
+    String index_page;
+    int response_code = 200;
+    if (webserver_get_file("index.html", index_page)) {
+        response_code = 404;
+    }
+    server.send(response_code, "text/html", index_page.c_str());
+}
+
+void update_data() {
+    data["system-ip-addr"] = WiFi.localIP().toString();
+    data["wifi-ssid"]      = WIFI_SSID;
+    data["wifi-rssi"]      = WiFi.RSSI();
+}
+
+void setup()
+{
+    pinMode(REMOTE_LED_PIN, OUTPUT);
+    digitalWrite(REMOTE_LED_PIN, LOW);
+    Serial.begin(115200);
+
+    delay(2000);
+    LOG_INFO("Booting");
+    for(uint8_t i = 10; i > 0; i--) {
+        LOG(".");
+        delay(150);
+    }
+    LOG("\n");
+
+    remote_devices = 0;
+
+    setup_wifi();
+    setup_fs();
+    setup_websocket();
+    setup_webserver();
+
+    LOG_INFO("Setup done\n");
+    digitalWrite(REMOTE_LED_PIN, HIGH);
+}
+
+void loop()
+{
+    web_socket.loop();
+    server.handleClient();
+}