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 }
