commit ee56cfa4320489244a526b60607950cbaa607f05
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date: Thu, 9 Apr 2026 11:41:58 -0300
Add files
Diffstat:
| A | README.md | | | 18 | ++++++++++++++++++ |
| A | data/index.html | | | 43 | +++++++++++++++++++++++++++++++++++++++++++ |
| A | data/main.js | | | 140 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | data/style.css | | | 41 | +++++++++++++++++++++++++++++++++++++++++ |
| A | platformio.ini | | | 19 | +++++++++++++++++++ |
| A | src/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();
+}