esp01s-serial-remote-controller

Serial communication from ESP-01S served webpage
Index Commits Files Refs README LICENSE
src/main.cpp (15798B)
   1 #include <Arduino_JSON.h>
   2 #include <ESP8266WiFi.h>
   3 #include <WebSocketsServer.h>
   4 #include <ESP8266WebServer.h>
   5 #include <LittleFS.h>
   6 
   7 #include "queue.h"
   8 
   9 #ifndef LED_BUILTIN
  10 #define LED_BUILTIN    2
  11 #endif
  12 
  13 #define REMOTE_LED_PIN 2
  14 #define LOG_ENABLE     false
  15 
  16 #ifndef WIFI_SSID
  17 #define WIFI_SSID "abcdefgh"
  18 #endif
  19 
  20 #ifndef WIFI_PASSWD
  21 #define WIFI_PASSWD "12345678"
  22 #endif
  23 
  24 #if true == LOG_ENABLE
  25 #define LOG(fmt, ...) do {             \
  26         Serial.printf(fmt, ##__VA_ARGS__); \
  27         Serial.flush();                    \
  28     } while (0)
  29 #else
  30 #define LOG(fmt, ...)
  31 #endif
  32 
  33 #define LOG_INFO(fmt, ...) LOG("[INFO] " fmt, ##__VA_ARGS__)
  34 #define LOG_ERROR(fmt, ...) LOG("[ERROR] " fmt, ##__VA_ARGS__)
  35 
  36 typedef enum {
  37     TX_DATA_STATUS             = 'A',
  38     TX_DATA_MAIN_OUTPUT_TOGGLE = 'B',
  39     TX_DATA_TIMER_TOGGLE       = 'C',
  40     TX_DATA_TIMER_SET_VALUES   = 'D',
  41     TX_DATA_TEMP_STATUS        = 'E',
  42     TX_DATA_TEMP_SET           = 'F'
  43 } uart_tx_data_e;
  44 
  45 typedef enum {
  46     RX_DATA_STATUS_OK             = 'A',
  47     RX_DATA_MAIN_OUTPUT_TOGGLE_OK = 'B',
  48     RX_DATA_TIMER_TOGGLE_OK       = 'C',
  49     RX_DATA_TIMER_SET_VALUES_OK   = 'D',
  50     RX_DATA_TEMP_STATUS_OK        = 'E',
  51     RX_DATA_TEMP_SET_OK           = 'F'
  52 } uart_rx_data_e;
  53 
  54 typedef enum {
  55     QUERY_STATUS = 0,
  56     QUERY_MAIN_OUTPUT_TOGGLE,
  57     QUERY_TIMER_TOGGLE,
  58     QUERY_TIMER_SET_VALUES,
  59     QUERY_TEMP_SET
  60 } query_type_e;
  61 
  62 typedef struct {
  63     uint8_t enabled;
  64     uint8_t from_hour;
  65     uint8_t from_minute;
  66     uint8_t to_hour;
  67     uint8_t to_minute;
  68 } timer_param_t;
  69 
  70 ESP8266WebServer server(80);
  71 WebSocketsServer web_socket = WebSocketsServer(81);
  72 uint8_t remote_devices, main_output_enabled;
  73 time_t system_time;
  74 timer_param_t timer, new_timer;
  75 JSONVar data, timer_data;
  76 queue_t uart_queue_tx, uart_queue_rx;
  77 int16_t temp_now, temp_now_new, temp_set_point, temp_set_point_new;
  78 
  79 void setup_wifi();
  80 void setup_websocket();
  81 void setup_webserver();
  82 void setup_fs();
  83 
  84 void websocket_event_handler(uint8_t num, WStype_t type, uint8_t *payload, size_t len);
  85 uint16_t webserver_get_file(String path, String &return_page);
  86 String webserver_file_content_type(String path);
  87 void webserver_file_handler();
  88 void webserver_handle_root();
  89 
  90 void update_data();
  91 void update_timer_data();
  92 
  93 void setup_wifi()
  94 {
  95     WiFi.begin(WIFI_SSID, WIFI_PASSWD);
  96 
  97     LOG_INFO("Connecting to WiFi");
  98 
  99     while (WiFi.status() != WL_CONNECTED)
 100     {
 101         delay(200);
 102         LOG(".");
 103     }
 104     LOG("\n");
 105 
 106     LOG_INFO("Connected to '%s', IP address %d.%d.%d.%d\n", WIFI_SSID,
 107              WiFi.localIP()[0],
 108              WiFi.localIP()[1],
 109              WiFi.localIP()[2],
 110              WiFi.localIP()[3]);
 111 }
 112 
 113 void setup_websocket()
 114 {
 115     web_socket.begin();
 116     web_socket.onEvent(websocket_event_handler);
 117 }
 118 
 119 void setup_webserver()
 120 {
 121     LOG_INFO("Loading server response from file 'index.html'\n");
 122 
 123     server.on("/", webserver_handle_root);
 124     server.onNotFound(webserver_file_handler);
 125 
 126     server.begin();
 127 }
 128 
 129 void setup_fs()
 130 {
 131     if (LittleFS.begin() == 0)
 132     {
 133         LOG_ERROR("Error couldn't begin filesystem!\n");
 134     }
 135 
 136     FSInfo fs_info;
 137     LittleFS.info(fs_info);
 138     LOG_INFO("LittleFS started %d bytes used\n", fs_info.usedBytes);
 139 }
 140 
 141 void webserver_update_all_clients_checkbox()
 142 {
 143     JSONVar output_data;
 144     String output_data_as_json;
 145 
 146     output_data["type"] = "cb";
 147     output_data["main-output-enabled"] = String(main_output_enabled);
 148     output_data["timer"]["enabled"] = String(timer.enabled);
 149 
 150     output_data_as_json = JSON.stringify(output_data);
 151     web_socket.broadcastTXT(output_data_as_json.c_str());
 152 }
 153 
 154 void webserver_update_all_clients_timer_values()
 155 {
 156     JSONVar output_data;
 157     String output_data_as_json;
 158 
 159     update_timer_data();
 160     output_data["type"] = "timer";
 161     output_data["timer"] = timer_data;
 162 
 163     // web_socket.broadcastTXT(JSON.stringify(output_data).c_str());
 164     output_data_as_json = JSON.stringify(output_data);
 165     web_socket.broadcastTXT(output_data_as_json.c_str());
 166 }
 167 
 168 void webserver_update_all_clients_temp_now_value()
 169 {
 170     JSONVar output_data;
 171     String output_data_as_json;
 172 
 173     output_data["type"] = "temp-now-values";
 174     output_data["temp-now"] = temp_now;
 175 
 176     // web_socket.broadcastTXT(JSON.stringify(output_data).c_str());
 177     output_data_as_json = JSON.stringify(output_data);
 178     web_socket.broadcastTXT(output_data_as_json.c_str());
 179 }
 180 
 181 
 182 void webserver_update_all_clients_temp_values()
 183 {
 184     JSONVar output_data;
 185     String output_data_as_json;
 186 
 187     output_data["type"] = "temp-values";
 188     output_data["temp-now"] = temp_now;
 189     output_data["temp-set"] = temp_set_point;
 190 
 191     // web_socket.broadcastTXT(JSON.stringify(output_data).c_str());
 192     output_data_as_json = JSON.stringify(output_data);
 193     web_socket.broadcastTXT(output_data_as_json.c_str());
 194 }
 195 
 196 void websocket_event_handler(uint8_t num, WStype_t type, uint8_t *payload, size_t len)
 197 {
 198     IPAddress ip;
 199     query_type_e query_type;
 200 
 201     switch(type)
 202     {
 203         case WStype_DISCONNECTED:
 204 
 205             LOG_INFO("%d: Disconnected\n", num);
 206             remote_devices -= 1;
 207             digitalWrite(REMOTE_LED_PIN, remote_devices ? LOW : HIGH);
 208 
 209         break;
 210 
 211         case WStype_CONNECTED:
 212 
 213             ip = web_socket.remoteIP(num);
 214             LOG_INFO("%u: Connected from %d.%d.%d.%d, URL '%s'\n",
 215                     num, ip[0], ip[1], ip[2], ip[3], payload);
 216             remote_devices += 1;
 217             digitalWrite(REMOTE_LED_PIN, remote_devices ? LOW : HIGH);
 218 
 219         break;
 220 
 221         case WStype_TEXT:
 222 
 223             query_type = (query_type_e)(*payload - '0');
 224             LOG_INFO("Received query_type '%s'\n", payload);
 225 
 226             switch(query_type)
 227             {
 228                 case QUERY_STATUS:
 229                     LOG_INFO("Enqueueing 'TX_DATA_STATUS'\n");
 230                     queue_enqueue(&uart_queue_tx, TX_DATA_STATUS);
 231                 break;
 232 
 233                 case QUERY_MAIN_OUTPUT_TOGGLE:
 234                     LOG_INFO("Enqueueing 'TX_DATA_MAIN_OUTPUT_TOGGLE'\n");
 235                     queue_enqueue(&uart_queue_tx, TX_DATA_MAIN_OUTPUT_TOGGLE);
 236                 break;
 237 
 238                 case QUERY_TIMER_TOGGLE:
 239                     LOG_INFO("Enqueueing 'TX_DATA_TIMER_TOGGLE'\n");
 240                     queue_enqueue(&uart_queue_tx, TX_DATA_TIMER_TOGGLE);
 241                 break;
 242 
 243                 case QUERY_TIMER_SET_VALUES:
 244                     payload++; // skip query type
 245                     timer_data = JSON.parse((char *)payload);
 246 
 247                     if(JSON.typeof(timer_data) == "undefined")
 248                     {
 249                         LOG_ERROR("QUERY_TIMER_SET_VALUES: Parsing payload failed!");
 250                         break;
 251                     }
 252 
 253                     new_timer.from_hour   = (uint8_t)String(timer_data["from"]["hour"]).toInt();
 254                     new_timer.from_minute = (uint8_t)String(timer_data["from"]["minute"]).toInt();
 255                     new_timer.to_hour     = (uint8_t)String(timer_data["to"]["hour"]).toInt();
 256                     new_timer.to_minute   = (uint8_t)String(timer_data["to"]["minute"]).toInt();
 257 
 258                     LOG_INFO("Enqueueing 'TX_DATA_TIMER_SET_VALUES'\n");
 259                     LOG_INFO("With values: %02d %02d  %02d %02d\n",
 260                                 new_timer.from_hour, new_timer.from_minute,
 261                                 new_timer.to_hour, new_timer.to_minute);
 262 
 263                     queue_enqueue(&uart_queue_tx, TX_DATA_TIMER_SET_VALUES);
 264                     queue_enqueue(&uart_queue_tx, new_timer.from_hour);
 265                     queue_enqueue(&uart_queue_tx, new_timer.from_minute);
 266                     queue_enqueue(&uart_queue_tx, new_timer.to_hour);
 267                     queue_enqueue(&uart_queue_tx, new_timer.to_minute);
 268 
 269                 break;
 270 
 271                 case QUERY_TEMP_SET: {
 272                     payload++; // skip query type
 273                     JSONVar temp_data;
 274                     temp_data = JSON.parse((char *)payload);
 275                     temp_set_point_new = (uint8_t)String(temp_data["temp_set_point_new"]).toInt();
 276                     queue_enqueue(&uart_queue_tx, TX_DATA_TEMP_SET);
 277                     queue_enqueue(&uart_queue_tx, (uint8_t)temp_set_point_new);
 278                 }
 279                 break;
 280 
 281                 default:
 282                 break;
 283             }
 284 
 285             break;
 286 
 287         case WStype_BIN:
 288 
 289             LOG_INFO("%u: get binary length: %u\n", num, len);
 290             hexdump(payload, len);
 291 
 292         break;
 293 
 294         case WStype_ERROR:
 295         default: break;
 296     }
 297 }
 298 
 299 // TODO: This should return server response codes, i.e. 200, 404, etc
 300 uint16_t webserver_get_file(String path, String &return_page)
 301 {
 302     if (LittleFS.exists(path))
 303     {
 304         LOG_INFO("Serving file '%s'\n", path.c_str());
 305         File file = LittleFS.open(path.c_str(), "r");
 306         while (file.available())
 307         {
 308             return_page += (char)file.read();
 309         }
 310         file.close();
 311     }
 312     else
 313     {
 314         LOG_INFO("'%s' File Not Found\n", path.c_str());
 315         return_page = R"==(<!DOCTYPE html>
 316         <html>
 317           <head>
 318               <title>ERROR 404: File Not found!!</title>
 319               <meta name="viewport" content="width=device-width, initial-scale=1.0">
 320           </head>
 321           <body>
 322               <h>ERROR 404: File Not Found!</h1>
 323               <p>file '/index.html' not found</p>
 324           </body>
 325         </html>)==";
 326         return 1;
 327     }
 328     return 0;
 329 }
 330 
 331 String webserver_file_content_type(String path)
 332 {
 333     if (path.endsWith(".html")) return "text/html";
 334     else if (path.endsWith(".css")) return "text/css";
 335     else if (path.endsWith(".js")) return "application/javascript";
 336     else if (path.endsWith(".ico")) return "image/x-icon";
 337     else if (path.endsWith(".gz")) return "application/x-gzip";
 338     return "text/plain";
 339 }
 340 
 341 void webserver_file_handler()
 342 {
 343     String path = server.uri();
 344     String requested_page;
 345     uint16_t response_code;
 346     response_code = webserver_get_file(path, requested_page) ? 404 : 200;
 347     server.send(response_code, webserver_file_content_type(path), requested_page);
 348 }
 349 
 350 void webserver_handle_root()
 351 {
 352     String index_page;
 353     uint16_t response_code = 200;
 354     if (webserver_get_file("index.html", index_page)) {
 355         response_code = 404;
 356     }
 357     server.send(response_code, "text/html", index_page.c_str());
 358 }
 359 
 360 String left_pad(uint8_t n) {
 361     return n < 10 ? "0" + String(n) : String(n);
 362 }
 363 
 364 void update_timer_data() {
 365     timer_data["enabled"]        = String(timer.enabled);
 366     timer_data["from"]["hour"]   = left_pad(timer.from_hour);
 367     timer_data["from"]["minute"] = left_pad(timer.from_minute);
 368     timer_data["to"]["hour"]     = left_pad(timer.to_hour);
 369     timer_data["to"]["minute"]   = left_pad(timer.to_minute);
 370 }
 371 
 372 void update_data() {
 373     data["main-output-enabled"] = String(main_output_enabled);
 374     data["system-ip-addr"]      = WiFi.localIP().toString();
 375     data["system-time"]         = (uint32_t)system_time;
 376     data["wifi-ssid"]           = WIFI_SSID;
 377     data["wifi-rssi"]           = WiFi.RSSI();
 378     update_timer_data();
 379     data["timer"] = timer_data;
 380     data["temp-now"] = temp_now;
 381     data["temp-set"] = temp_set_point;
 382 }
 383 
 384 void uart_rx_handler(void)
 385 {
 386     String data_as_json;
 387     uint8_t buffer_aux[4];
 388 
 389     /*
 390     uart_rx_data_e rx_data_type = (uart_rx_data_e)queue_dequeue(&uart_queue_rx);
 391     */
 392 
 393     uint8_t rx_data_type = queue_peek(&uart_queue_rx);
 394 
 395     if ((rx_data_type == RX_DATA_STATUS_OK) && (queue_count(&uart_queue_rx) < 13))
 396     {
 397         // Wait for additional data to arrive
 398         return;
 399     }
 400 
 401     if ((rx_data_type == RX_DATA_TEMP_STATUS_OK) && (queue_count(&uart_queue_rx) < 2))
 402     {
 403         // Wait for additional data to arrive
 404         return;
 405     }
 406 
 407     queue_dequeue(&uart_queue_rx);
 408 
 409     switch(rx_data_type)
 410     {
 411         case RX_DATA_STATUS_OK:
 412 
 413             main_output_enabled = queue_dequeue(&uart_queue_rx);
 414 
 415             // LSB first
 416             buffer_aux[0] = queue_dequeue(&uart_queue_rx);
 417             buffer_aux[1] = queue_dequeue(&uart_queue_rx);
 418             buffer_aux[2] = queue_dequeue(&uart_queue_rx);
 419             buffer_aux[3] = queue_dequeue(&uart_queue_rx);
 420 
 421             system_time = ((uint32_t)buffer_aux[3] << 24) |
 422                           ((uint32_t)buffer_aux[2] << 16) |
 423                           ((uint32_t)buffer_aux[1] << 8)  |
 424                           ((uint32_t)buffer_aux[0]);
 425 
 426             timer.enabled     = queue_dequeue(&uart_queue_rx);
 427             timer.from_hour   = queue_dequeue(&uart_queue_rx);
 428             timer.from_minute = queue_dequeue(&uart_queue_rx);
 429             timer.to_hour     = queue_dequeue(&uart_queue_rx);
 430             timer.to_minute   = queue_dequeue(&uart_queue_rx);
 431 
 432             temp_now       = queue_dequeue(&uart_queue_rx);
 433             temp_set_point = queue_dequeue(&uart_queue_rx);
 434 
 435             update_data();
 436             data["type"] = "all";
 437 
 438             // TODO: Use sendTXT instead of braodcastTXT; resolve socket numb
 439             // web_socket.sendTXT(num, data_as_json);
 440             data_as_json = JSON.stringify(data);
 441             web_socket.broadcastTXT(data_as_json.c_str());
 442             LOG_INFO("%s\n", data_as_json.c_str());
 443 
 444         break;
 445         case RX_DATA_MAIN_OUTPUT_TOGGLE_OK:
 446 
 447             main_output_enabled = !main_output_enabled;
 448             webserver_update_all_clients_checkbox();
 449 
 450         break;
 451 
 452         case RX_DATA_TIMER_TOGGLE_OK:
 453 
 454             timer.enabled = !timer.enabled;
 455             webserver_update_all_clients_checkbox();
 456 
 457         break;
 458 
 459         case RX_DATA_TIMER_SET_VALUES_OK:
 460 
 461             timer.from_hour   = new_timer.from_hour;
 462             timer.from_minute = new_timer.from_minute;
 463             timer.to_hour     = new_timer.to_hour;
 464             timer.to_minute   = new_timer.to_minute;
 465             webserver_update_all_clients_timer_values();
 466 
 467         break;
 468 
 469         case RX_DATA_TEMP_SET_OK:
 470 
 471             temp_set_point = temp_set_point_new;
 472             webserver_update_all_clients_temp_values();
 473 
 474         break;
 475 
 476         case RX_DATA_TEMP_STATUS_OK:
 477 
 478             temp_now_new = queue_dequeue(&uart_queue_rx);
 479             temp_set_point = queue_dequeue(&uart_queue_rx); // discard value
 480 
 481             if (temp_now_new != temp_now)
 482             {
 483                 temp_now = temp_now_new;
 484                 webserver_update_all_clients_temp_now_value();
 485             }
 486 
 487         break;
 488 
 489         default:
 490             queue_dequeue(&uart_queue_rx);
 491         break;
 492     }
 493 }
 494 
 495 void setup()
 496 {
 497     Serial.begin(115200);
 498 
 499     delay(2000);
 500     LOG_INFO("Booting");
 501     for(uint8_t i = 10; i > 0; i--) {
 502         LOG(".");
 503         delay(150);
 504     }
 505     LOG("\n");
 506 
 507     pinMode(REMOTE_LED_PIN, OUTPUT);
 508     digitalWrite(REMOTE_LED_PIN, LOW);
 509 
 510     remote_devices = 0;
 511 
 512     timer.enabled = 0;
 513     timer.from_hour = 12;
 514     timer.from_minute = 0;
 515     timer.to_hour = 0;
 516     timer.to_minute = 0;
 517 
 518     main_output_enabled = 0;
 519 
 520     temp_now = 0;
 521     temp_now_new = 0;
 522     temp_set_point = 0;
 523     temp_set_point_new = 0;
 524 
 525     time(&system_time); // read the current time
 526 
 527     setup_wifi();
 528     setup_fs();
 529     setup_websocket();
 530     setup_webserver();
 531 
 532     LOG_INFO("Setup done\n");
 533     digitalWrite(REMOTE_LED_PIN, HIGH);
 534 
 535     queue_init(&uart_queue_tx);
 536     queue_init(&uart_queue_rx);
 537 }
 538 
 539 void loop()
 540 {
 541     while (Serial.available() > 0)
 542     {
 543         int16_t byte = Serial.read();
 544 
 545         if (byte >= 0)
 546         {
 547             queue_enqueue(&uart_queue_rx, (uint8_t)byte);
 548         }
 549     }
 550 
 551     if (!queue_is_empty(&uart_queue_rx))
 552     {
 553         uart_rx_handler();
 554     }
 555 
 556     while (!queue_is_empty(&uart_queue_tx))
 557     {
 558         uint8_t value = queue_dequeue(&uart_queue_tx);
 559         LOG_INFO("Sending byte 0x%02X...\n", value);
 560         Serial.write(value);
 561         LOG("\n");
 562     }
 563 
 564     web_socket.loop();
 565     server.handleClient();
 566 }