esp01s-serial-remote-controller

Serial communication from ESP-01S served webpage
Index Commits Files Refs README LICENSE
commit 9b69e9da6d77e4b0b2589bf010f5cc8f2dc47f47
parent ee56cfa4320489244a526b60607950cbaa607f05
Author: Martin Kloeckner <mjkloeckner@gmail.com>
Date:   Fri, 10 Apr 2026 23:46:03 -0300

Implement UART TX/RX data share between main MCU

Diffstat:
Mdata/index.html | 89++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdata/main.js | 57+++++++++++++++++++++++++++++++--------------------------
Mdata/style.css | 243+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Ainclude/queue.h | 31+++++++++++++++++++++++++++++++
Mplatformio.ini | 1+
Msrc/main.cpp | 260++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Asrc/queue.c | 45+++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 664 insertions(+), 62 deletions(-)
diff --git a/data/index.html b/data/index.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
     <head>
-        <title>ESP8266 Remote Panel</title>
+        <title>ESP8266 Web Server</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">
@@ -10,6 +10,85 @@
     <body>
         <div id="main-div">
             <h1 id="title">ESP8266 Remote Panel</h1>
+            <div id="buttons-div">
+                <div id="main-toggle-div" onclick="handle_click('main-toggle')">
+                    <table>
+                        <tr style="vertical-align: bottom;">
+                            <td>
+                                Main output enabled
+                            </td>
+                            <td id="main-checkbox-td">
+                                <div id="main-checkbox" checked="">
+                                    <svg id="checkmark-svg"
+                                     version="1.0"
+                                     xmlns="http://www.w3.org/2000/svg"
+                                     viewBox="0 0 64.000000 64.000000"
+                                     preserveAspectRatio="xMidYMid meet">
+                                        <g transform="translate(0.000000,64.000000) scale(0.100000,-0.100000)"
+                                        fill="currentColor" stroke="currentColor">
+                                        <path d="M380 419 l-142 -211 -67 66 c-38 38 -75 66 -87 66 -25 0 -84 -60 -84
+                                        -86 0 -11 45 -65 113 -132 121 -121 145 -132 184 -89 49 54 343 502 343 523 0
+                                        27 -62 74 -96 74 -16 0 -53 -48 -164 -211z"/>
+                                        </g>
+                                    </svg>
+                                </div>
+                            </td>
+                        </tr>
+                    </table>
+                </div>
+                <div id="timer">
+                    <div id="timer-enable" onclick="handle_click('timer-toggle')">
+                        <table>
+                            <tr>
+                                <td>
+                                    Timer enabled
+                                </td>
+                                <td id="timer-checkbox-td">
+                                    <div id="timer-checkbox" checked="">
+                                        <svg id="checkmark-svg"
+                                         version="1.0"
+                                         xmlns="http://www.w3.org/2000/svg"
+                                         viewBox="0 0 64.000000 64.000000"
+                                         preserveAspectRatio="xMidYMid meet">
+                                            <g transform="translate(0.000000,64.000000) scale(0.100000,-0.100000)"
+                                            fill="currentColor" stroke="currentColor">
+                                            <path d="M380 419 l-142 -211 -67 66 c-38 38 -75 66 -87 66 -25 0 -84 -60 -84
+                                            -86 0 -11 45 -65 113 -132 121 -121 145 -132 184 -89 49 54 343 502 343 523 0
+                                            27 -62 74 -96 74 -16 0 -53 -48 -164 -211z"/>
+                                            </g>
+                                        </svg>
+                                    </div>
+                                </td>
+                            </tr>
+                        </table>
+                    </div>
+                    <div id="timer-sep"></div>
+                    <div id="timer-picker">
+                        <table>
+                            <tr>
+                                <td>
+                                    From
+                                </td>
+                                <td>
+                                    <input id="from-time" type="time" value="18:00"/>
+                                </td>
+                            </tr>
+                            <tr>
+                                <td>
+                                    Until
+                                </td>
+                                <td>
+                                    <input id="to-time" type="time" value="00:00"/>
+                                </td>
+                            </tr>
+                        </table>
+                    </div>
+                    <div id="timer-save-div">
+                        <input type="button" value="Save" id="timer-save">
+                    </div>
+                </div>
+            </div>
+
             <div id="info-div">
                 <table style="width: 100%; padding: 1em;">
                     <tr>
@@ -36,6 +115,14 @@
                             <a id="system-ip-addr"></a>
                         </td>
                     </tr>
+                    <tr>
+                        <td>
+                            System time
+                        </td>
+                        <td>
+                            <div id="system-time"></div>
+                        </td>
+                    </tr>
                 </table>
             </div>
         </div>
diff --git a/data/main.js b/data/main.js
@@ -1,6 +1,8 @@
 var received_data;
-var time_div;
-var system_time, ip_addr, wifi_ssid, wifi_rssi;
+var main_checkbox, timer_checkbox;
+var time_div, system_ip_addr, wifi_ssid, wifi_rssi;
+var system_time, system_time_elem;
+var to_time, from_time, timer_save_button;
 
 const time_formatter = new Intl.DateTimeFormat('en-GB', {
     timeZone: 'America/Argentina/Buenos_Aires',
@@ -13,7 +15,10 @@ const time_formatter = new Intl.DateTimeFormat('en-GB', {
 });
 
 const query_types = {
-    "QUERY_STATUS": 0,
+    "QUERY_STATUS":             0,
+    "QUERY_MAIN_OUTPUT_TOGGLE": 1,
+    "QUERY_TIMER_TOGGLE":       2,
+    "QUERY_TIMER_SET_VALUES":   3
 }
 
 function rssi_to_percentage(rssi) {
@@ -29,7 +34,6 @@ function rssi_to_percentage(rssi) {
     return quality; 
 }
 
-/*
 function update_checkbox() {
     main_checkbox.setAttribute("checked", received_data["main-output-enabled"]);
     timer_checkbox.setAttribute("checked", received_data["timer"]["enabled"]);
@@ -48,28 +52,15 @@ function update_timer() {
     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.href = received_data["system-ip-addr"];
     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) + "%)";
@@ -77,11 +68,21 @@ function update_all() {
 
 function socket_onmessage_handler(event) {
     received_data = JSON.parse(event.data);
+    console.log(received_data);
 
     switch(received_data["type"]) {
         case "all":
             update_all();
             break;
+        case "time":
+            update_time();
+            break;
+        case "cb":
+            update_checkbox();
+            break;
+        case "timer":
+            update_timer();
+            break;
         default:
             console.log("[SOCKET] received_data: type not known, updating all...");
             update_all();
@@ -93,21 +94,18 @@ function socket_onopen_handler(event) {
     socket.send(query_types["QUERY_STATUS"]);
 }
 
-/*
 function handle_click(cb) {
     if(cb == 'main-toggle') {
-        socket.send(query_types["MAIN_OUTPUT_TOGGLE"]);
+        socket.send(query_types["QUERY_MAIN_OUTPUT_TOGGLE"]);
     } else if(cb == 'timer-toggle') {
-        socket.send(query_types["TIMER_TOGGLE"]);
+        socket.send(query_types["QUERY_TIMER_TOGGLE"]);
     } else {
         console.log("handle_click: wrong target");
     }
 }
-*/
 
-/*
 function update_system_time() {
-    socket.send(query_types["MAIN_OUTPUT_STATUS"]);
+    socket.send(query_types["QUERY_STATUS"]);
 }
 
 function timer_set_time() {
@@ -121,16 +119,23 @@ function timer_set_time() {
     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));
+    socket.send(query_types["QUERY_TIMER_SET_VALUES"] + JSON.stringify(query_json));
 }
-*/
 
 function query_data() {
+    main_checkbox = document.getElementById("main-checkbox");
+    timer_checkbox = document.getElementById("timer-checkbox");
     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");
 
+    from_time = document.getElementById("from-time");
+    to_time = document.getElementById("to-time");
+
+    timer_save_button = document.getElementById("timer-save");
+    timer_save_button.addEventListener( "click", timer_set_time, false);
+
     // setInterval(update_system_time, 30*1000); // update time every 30 secs
 }
 
diff --git a/data/style.css b/data/style.css
@@ -1,22 +1,134 @@
 body {
     margin: 3em auto 3em auto;
-    /* font-family: "Nimbus Sans", "Nimbus Sans L", "Helvetica", Inter; */
-    font-family: monospace;
-    font-size: 18px;
+    text-align: center;
+    font-family: "Nimbus Sans", "Nimbus Sans L", "Helvetica", Inter;
+    font-size: 20px;
+}
+
+#title {
+    padding: 0em 0.5em 0em 0.5em;
 }
 
 #main-div {
-    max-width: 640px;
+    max-width: 23em;
+    margin: auto auto;
+    padding: 0 10px;
+}
+
+table {
+    border-collapse: collapse;
+}
+
+tr {
+    vertical-align: bottom;
+    border-color: #3E3E42;
+}
+
+td {
+    width: 100%;
+}
+
+a, a:visited {
+    color: #00E;
+}
+
+h1 {
+    font-size: 36px;
+}
+
+#main-toggle-div {
+    margin-bottom: 0.5em;
+    cursor: pointer;
+    padding: 1.25em 28px 1.00em 28px;
+    border-radius: 8px;
+    border: 1px solid #E3E3E3;
+}
+
+#timer {
+    border-radius: 8px;
+    border: 1px solid #E3E3E3;
+}
+
+#timer-enable {
+    cursor: pointer;
+    padding: 27.5px 28px 16px 28px;
+}
+
+#timer-picker {
+    padding: 18px 28px 0.00em 28px;
+    font-size: 20px;
+}
+
+#timer-sep {
+    border-bottom: 1px solid #E3E3E3;
+    margin: 0 30px 0 30px;
+}
+
+#from-time, #to-time {
+    font-size: 18px;
+    padding: 2px;
+    margin: 0px;
+    border: 1px solid #E3E3E3;
+    border-radius: 5px;
+    cursor: pointer;
+    background-color: white;
+}
+
+#timer-save-div {
+    padding: 18px 30px 30px 30px;
     text-align: center;
+}
+
+#timer-save {
+    margin-right: 0px;
+    box-shadow: none;
+    border: 1px solid #E3E3E3;
+    color: black;
+    background-color: white;
+    border-radius: 5px;
+    cursor: pointer;
+    height: 32px;
+    font-size: 14px;
+    width: 100%;
+    font-weight: bold;
+}
+
+#timer-save:active {
+  background-color: #E3E3E3;
+  border: none;
+}
+
+@media (hover: hover) and (pointer: fine) {
+    #main-toggle-div:hover #main-checkbox,
+    #timer-enable:hover #timer-checkbox,
+    #from-time:hover, #to-time:hover,
+    #main-checkbox:hover, #timer-checkbox:hover,
+    #timer-save:hover {
+        border: 1px solid #999999;
+    }
+}
+
+#from-time:focus-visible,
+#to-time:focus-visible {
+    border: 1px solid #999999;
+    outline: none;
+}
+
+#buttons-div {
+    text-align: left;
     margin-left: auto;
     margin-right: auto;
-    padding: 12px;
+    font-size: 22px;
+    user-select: none;
 }
 
 #info-div {
-    border: 1px solid black;
-    border-radius: 12px;
-
+    padding: 1.75em 1em 1em 1em;
+    font-size: 16px;
+    td {
+        padding: .20em;
+        text-wrap: nowrap;
+    }
     td:nth-child(1) {
         text-align: left;
         user-select: none;
@@ -27,15 +139,122 @@ body {
     }
 }
 
+#save-button {
+    padding: 0.25em 0 0 0;
+    text-align: right;
+}
+
+#save-timer-button {
+    border: 1px solid grey;
+    border-radius: 2px;
+    text-align: center;
+    margin-top: 0.35em;
+    font-size: 16px;
+    color: black;
+    background-color: white;
+    vertical-align: bottom;
+}
+
+#main-checkbox, #timer-checkbox {
+    width: 20px;
+    height: 20px;
+    border: 1px solid #E3E3E3;
+    border-radius: 5px;
+    color: black;
+    text-align: center;
+    vertical-align: center;
+    cursor: pointer;
+    color: white;
+    svg {
+        display: none;
+    }
+}
+
+#main-checkbox[checked="1"], #timer-checkbox[checked="1"] {
+    background-color: #3584e4;
+    svg {
+        display: revert;
+    }
+}
+
+#checkmark-svg {
+    width: 14px;
+    height: 20px;
+}
+
+#main-checkbox-td, #timer-checkbox-td {
+    display: block;
+    padding: 0;
+    margin: 0;
+}
+
 @media (prefers-color-scheme: dark) {
     body {
         color: white;
         background-color: #17171A;
     }
-    a {
-        color: skyblue;
+
+    a, a:visited {
+        color: #56C8FF;
     }
-    #info-div {
-        border-color: grey;
+
+    input {
+        color-scheme: dark;
+    }
+
+    #buttons-div {
+        border: none;
+    }
+
+    #main-toggle-div, #timer {
+        background-color: #232326;
+        border: none;
+    }
+
+    #timer-sep {
+        border-bottom-color: #3E3E42;
+    }
+
+    #timer-save {
+        border-color: #999999;
+        color: white;
+        background-color: #303033;
+        border-color: #3E3E42;
+    }
+
+    #timer-save:active {
+        background-color: #3E3E42;
+        border: none;
+    }
+
+    #from-time, #to-time {
+        background-color: #303033;
+        color: white;
+        border-color: #3E3E42;
+    }
+
+    #main-checkbox, #timer-checkbox {
+        border-color: #3E3E42;
+        background-color: #303033;
+    }
+
+    #main-checkbox[checked="1"], #timer-checkbox[checked="1"] {
+        background-color: #56C8FF;
+        color: #004263;
+    }
+
+    @media (hover: hover) and (pointer: fine) {
+        #main-toggle-div:hover #main-checkbox,
+        #timer-enable:hover #timer-checkbox,
+        #from-time:hover, #to-time:hover,
+        #main-checkbox:hover, #timer-checkbox:hover,
+        #timer-save:hover {
+            border: 1px solid #828282;
+        }
+    }
+
+    #from-time:focus-visible,
+    #to-time:focus-visible {
+        border: 1px solid #828282;
     }
 }
diff --git a/include/queue.h b/include/queue.h
@@ -0,0 +1,31 @@
+
+#ifndef QUEUE_H
+#define QUEUE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+#define QUEUE_LEN 255
+
+typedef struct {
+    uint8_t head;
+    uint8_t tail;
+    uint8_t count;
+    uint8_t data[QUEUE_LEN];
+} queue_t;
+
+void queue_init(queue_t *queue);
+void queue_enqueue(queue_t *queue, uint8_t value);
+uint8_t queue_dequeue(queue_t *queue);
+uint8_t queue_peek(queue_t *queue);
+uint8_t queue_count(queue_t *queue);
+uint8_t queue_is_empty(queue_t *queue);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // QUEUE_H
diff --git a/platformio.ini b/platformio.ini
@@ -3,6 +3,7 @@ platform = espressif8266
 framework = arduino
 board = esp01
 upload_protocol = esptool
+upload_speed = 921600
 lib_deps =
     links2004/WebSockets
     arduino-libraries/Arduino_JSON
diff --git a/src/main.cpp b/src/main.cpp
@@ -1,10 +1,11 @@
-#include <Arduino.h>
 #include <Arduino_JSON.h>
 #include <ESP8266WiFi.h>
 #include <WebSocketsServer.h>
 #include <ESP8266WebServer.h>
 #include <LittleFS.h>
 
+#include "queue.h"
+
 #ifndef LED_BUILTIN
 #define LED_BUILTIN    2
 #endif
@@ -12,7 +13,7 @@
 #define REMOTE_LED_PIN 2
 #define WIFI_SSID      "abcdefgh"
 #define WIFI_PASSWD    "12345678"
-#define LOG_ENABLE     true
+#define LOG_ENABLE     false
 
 #if true == LOG_ENABLE
 #define LOG(fmt, ...) do {             \
@@ -27,13 +28,42 @@
 #define LOG_ERROR(fmt, ...) LOG("[ERROR] " fmt, ##__VA_ARGS__)
 
 typedef enum {
-    QUERY_STATUS
-} query_t;
+    TX_DATA_STATUS             = 'A',
+    TX_DATA_MAIN_OUTPUT_TOGGLE = 'B',
+    TX_DATA_TIMER_TOGGLE       = 'C',
+    TX_DATA_TIMER_SET_VALUES   = 'D',
+} uart_tx_data_e;
 
-ESP8266WebServer server(80);
-WebSocketsServer web_socket = WebSocketsServer(81);
-static uint8_t remote_devices;
-JSONVar data;
+typedef enum {
+    RX_DATA_STATUS_OK             = 'A',
+    RX_DATA_MAIN_OUTPUT_TOGGLE_OK = 'B',
+    RX_DATA_TIMER_TOGGLE_OK       = 'C',
+    RX_DATA_TIMER_SET_VALUES_OK   = 'D'
+} uart_rx_data_e;
+
+typedef enum {
+    QUERY_STATUS = 0,
+    QUERY_MAIN_OUTPUT_TOGGLE,
+    QUERY_TIMER_TOGGLE,
+    QUERY_TIMER_SET_VALUES
+} query_type_e;
+
+typedef struct {
+    uint8_t enabled;
+    uint8_t from_hour;
+    uint8_t from_minute;
+    uint8_t to_hour;
+    uint8_t to_minute;
+} timer_param_t;
+
+static ESP8266WebServer server(80);
+static WebSocketsServer web_socket = WebSocketsServer(81);
+static uint8_t remote_devices, main_output_enabled;
+uint32_t t, system_time_dt;
+static time_t system_time;
+static timer_param_t timer, new_timer;
+static JSONVar data, timer_data;
+static queue_t uart_queue_tx, uart_queue_rx;
 
 void setup_wifi();
 void setup_websocket();
@@ -41,12 +71,13 @@ 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);
+uint16_t 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 update_timer_data();
 
 void setup_wifi()
 {
@@ -95,11 +126,37 @@ void setup_fs()
     LOG_INFO("LittleFS started %d bytes used\n", fs_info.usedBytes);
 }
 
+void webserver_update_all_clients_checkbox()
+{
+    JSONVar output_data;
+    String output_data_as_json;
+
+    output_data["type"] = "cb";
+    output_data["main-output-enabled"] = String(main_output_enabled);
+    output_data["timer"]["enabled"] = String(timer.enabled);
+
+    output_data_as_json = JSON.stringify(output_data);
+    web_socket.broadcastTXT(output_data_as_json);
+}
+
+void webserver_update_all_clients_timer_values()
+{
+    JSONVar output_data;
+    String output_data_as_json;
+
+    update_timer_data();
+    output_data["type"] = "timer";
+    output_data["timer"] = timer_data;
+
+    // web_socket.broadcastTXT(JSON.stringify(output_data).c_str());
+    output_data_as_json = JSON.stringify(output_data);
+    web_socket.broadcastTXT(output_data_as_json);
+}
+
 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;
+    query_type_e query_type;
 
     switch(type)
     {
@@ -123,18 +180,47 @@ void websocket_event_handler(uint8_t num, WStype_t type, uint8_t *payload, size_
 
         case WStype_TEXT:
 
-            query_type = *payload - '0';
+            query_type = (query_type_e)(*payload - '0');
+            LOG_INFO("Received query_type '%s'\n", payload);
+
             switch(query_type)
             {
                 case QUERY_STATUS:
-                    update_data();
-                    data["type"] = "all";
-                    data_as_json = JSON.stringify(data);
-                    web_socket.sendTXT(num, data_as_json);
+                    queue_enqueue(&uart_queue_tx, TX_DATA_STATUS);
+                break;
+
+                case QUERY_MAIN_OUTPUT_TOGGLE:
+                    queue_enqueue(&uart_queue_tx, TX_DATA_MAIN_OUTPUT_TOGGLE);
+                break;
+
+                case QUERY_TIMER_TOGGLE:
+                    queue_enqueue(&uart_queue_tx, TX_DATA_TIMER_TOGGLE);
+                break;
+
+                case QUERY_TIMER_SET_VALUES:
+                    payload++; // skip query type
+                    timer_data = JSON.parse((char *)payload);
+
+                    if(JSON.typeof(timer_data) == "undefined")
+                    {
+                        LOG_ERROR("QUERY_TIMER_SET_VALUES: Parsing payload failed!");
+                        break;
+                    }
+
+                    new_timer.from_hour   = (uint8_t)String(timer_data["from"]["hour"]).toInt();
+                    new_timer.from_minute = (uint8_t)String(timer_data["from"]["minute"]).toInt();
+                    new_timer.to_hour     = (uint8_t)String(timer_data["to"]["hour"]).toInt();
+                    new_timer.to_minute   = (uint8_t)String(timer_data["to"]["minute"]).toInt();
+
+                    queue_enqueue(&uart_queue_tx, TX_DATA_TIMER_SET_VALUES);
+                    queue_enqueue(&uart_queue_tx, new_timer.from_hour);
+                    queue_enqueue(&uart_queue_tx, new_timer.from_minute);
+                    queue_enqueue(&uart_queue_tx, new_timer.to_hour);
+                    queue_enqueue(&uart_queue_tx, new_timer.to_minute);
+
                 break;
 
                 default:
-                    LOG_INFO(" %s\n", payload);
                 break;
             }
 
@@ -152,7 +238,8 @@ void websocket_event_handler(uint8_t num, WStype_t type, uint8_t *payload, size_
     }
 }
 
-int webserver_get_file(String path, String &return_page)
+// TODO: This should return server response codes, i.e. 200, 404, etc
+uint16_t webserver_get_file(String path, String &return_page)
 {
     if (LittleFS.exists(path))
     {
@@ -197,7 +284,7 @@ void webserver_file_handler()
 {
     String path = server.uri();
     String requested_page;
-    int response_code;
+    uint16_t response_code;
     response_code = webserver_get_file(path, requested_page) ? 404 : 200;
     server.send(response_code, webserver_file_content_type(path), requested_page);
 }
@@ -205,17 +292,113 @@ void webserver_file_handler()
 void webserver_handle_root()
 {
     String index_page;
-    int response_code = 200;
+    uint16_t response_code = 200;
     if (webserver_get_file("index.html", index_page)) {
         response_code = 404;
     }
     server.send(response_code, "text/html", index_page.c_str());
 }
 
+String left_pad(uint8_t n) {
+    return n < 10 ? "0" + String(n) : String(n);
+}
+
+void update_timer_data() {
+    timer_data["enabled"]        = String(timer.enabled);
+    timer_data["from"]["hour"]   = left_pad(timer.from_hour);
+    timer_data["from"]["minute"] = left_pad(timer.from_minute);
+    timer_data["to"]["hour"]     = left_pad(timer.to_hour);
+    timer_data["to"]["minute"]   = left_pad(timer.to_minute);
+}
+
 void update_data() {
-    data["system-ip-addr"] = WiFi.localIP().toString();
-    data["wifi-ssid"]      = WIFI_SSID;
-    data["wifi-rssi"]      = WiFi.RSSI();
+    data["main-output-enabled"] = String(main_output_enabled);
+    data["system-ip-addr"]      = WiFi.localIP().toString();
+    data["system-time"]         = (uint32_t)system_time;
+    data["wifi-ssid"]           = WIFI_SSID;
+    data["wifi-rssi"]           = WiFi.RSSI();
+    update_timer_data();
+    data["timer"] = timer_data;
+}
+
+void uart_rx_handler(void)
+{
+    String data_as_json;
+    uint8_t buffer_aux[4];
+    uart_rx_data_e rx_data_type = (uart_rx_data_e)queue_peek(&uart_queue_rx);
+    /* 
+    uart_rx_data_e rx_data_type = (uart_rx_data_e)queue_dequeue(&uart_queue_rx);
+    */
+
+    // Wait for additional data to arrive
+    if ((rx_data_type == RX_DATA_STATUS_OK) && (8 >= queue_count(&uart_queue_rx)))
+    {
+        return;
+    }
+    else
+    {
+        queue_dequeue(&uart_queue_tx);
+    }
+
+    switch(rx_data_type)
+    {
+        case RX_DATA_STATUS_OK:
+
+            main_output_enabled = queue_dequeue(&uart_queue_rx);
+
+            // LSB first
+            buffer_aux[0] = queue_dequeue(&uart_queue_rx);
+            buffer_aux[1] = queue_dequeue(&uart_queue_rx);
+            buffer_aux[2] = queue_dequeue(&uart_queue_rx);
+            buffer_aux[3] = queue_dequeue(&uart_queue_rx);
+
+            system_time = ((uint32_t)buffer_aux[3] << 24) |
+                          ((uint32_t)buffer_aux[2] << 16) |
+                          ((uint32_t)buffer_aux[1] << 8)  |
+                          ((uint32_t)buffer_aux[0]);
+
+            timer.enabled     = queue_dequeue(&uart_queue_rx);
+            timer.from_hour   = queue_dequeue(&uart_queue_rx);
+            timer.from_minute = queue_dequeue(&uart_queue_rx);
+            timer.to_hour     = queue_dequeue(&uart_queue_rx);
+            timer.to_minute   = queue_dequeue(&uart_queue_rx);
+
+            update_data();
+            data["type"] = "all";
+
+            // TODO: Use sendTXT instead of braodcastTXT; resolve socket numb
+            // web_socket.sendTXT(num, data_as_json);
+            data_as_json = JSON.stringify(data);
+            web_socket.broadcastTXT(data_as_json);
+
+        break;
+        case RX_DATA_MAIN_OUTPUT_TOGGLE_OK:
+
+            main_output_enabled = !main_output_enabled;
+            webserver_update_all_clients_checkbox();
+
+        break;
+
+        case RX_DATA_TIMER_TOGGLE_OK:
+
+            timer.enabled = !timer.enabled;
+            webserver_update_all_clients_checkbox();
+
+        break;
+
+        case RX_DATA_TIMER_SET_VALUES_OK:
+
+            timer.from_hour   = new_timer.from_hour;
+            timer.from_minute = new_timer.from_minute;
+            timer.to_hour     = new_timer.to_hour;
+            timer.to_minute   = new_timer.to_minute;
+            webserver_update_all_clients_timer_values();
+
+        break;
+
+        default:
+        break;
+    }
 }
 
 void setup()
@@ -223,6 +406,7 @@ void setup()
     pinMode(REMOTE_LED_PIN, OUTPUT);
     digitalWrite(REMOTE_LED_PIN, LOW);
     Serial.begin(115200);
+    Serial.write("\n\r\n\r\n\r");
 
     delay(2000);
     LOG_INFO("Booting");
@@ -234,6 +418,18 @@ void setup()
 
     remote_devices = 0;
 
+    timer.enabled = 0;
+    timer.from_hour = 12;
+    timer.from_minute = 0;
+    timer.to_hour = 0;
+    timer.to_minute = 0;
+
+    main_output_enabled = 0;
+    t = 0;
+    system_time_dt = 0;
+
+    time(&system_time); // read the current time
+
     setup_wifi();
     setup_fs();
     setup_websocket();
@@ -241,10 +437,28 @@ void setup()
 
     LOG_INFO("Setup done\n");
     digitalWrite(REMOTE_LED_PIN, HIGH);
+
+    queue_init(&uart_queue_tx);
+    queue_init(&uart_queue_rx);
 }
 
 void loop()
 {
+    while (Serial.available() > 0)
+    {
+        queue_enqueue(&uart_queue_rx, Serial.read());
+    }
+
+    if (!queue_is_empty(&uart_queue_rx))
+    {
+        uart_rx_handler();
+    }
+
+    while (!queue_is_empty(&uart_queue_tx) && (Serial.availableForWrite() > 0))
+    {
+        Serial.write(queue_dequeue(&uart_queue_tx));
+    }
+
     web_socket.loop();
     server.handleClient();
 }
diff --git a/src/queue.c b/src/queue.c
@@ -0,0 +1,45 @@
+#include "queue.h"
+
+void queue_init(queue_t *queue)
+{
+    queue->head = 0;
+    queue->tail = 0;
+    queue->count = 0;
+
+    for (uint8_t i = 0; i < QUEUE_LEN; ++i)
+    {
+        queue->data[i] = 0;
+    }
+}
+
+void queue_enqueue(queue_t *queue, uint8_t value)
+{
+    queue->count++;
+    queue->data[queue->head++] = value;
+}
+
+uint8_t queue_dequeue(queue_t *queue)
+{
+    if (queue_is_empty(queue))
+    {
+        return queue_peek(queue);
+    }
+
+    queue->count--;
+    return queue->data[queue->tail++];
+}
+
+uint8_t queue_peek(queue_t *queue)
+{
+    return queue->data[queue->tail];
+}
+
+uint8_t queue_count(queue_t *queue)
+{
+    return queue->count;
+}
+
+uint8_t queue_is_empty(queue_t *queue)
+{
+    return (queue->count == 0);
+}